git.delta.rocks / unique-network / refs/commits / e0e89b163c74

difftreelog

Merge pull request #32 from usetech-llc/feature/nftpar_118

Greg Zaitsev2020-12-15parents: #c313f34 #2b0cd0f.patch.diff
in: master
NFTPAR-118. Per Collection Limits

4 files changed

modifiednode/src/chain_spec.rsdiffbeforeafterboth
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -171,7 +171,8 @@
                     sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
                     unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
                     const_on_chain_schema: vec![],
-                    variable_on_chain_schema: vec![]
+					variable_on_chain_schema: vec![],
+					limits: CollectionLimits::default()
                 },
             )],
             nft_item_id: vec![],
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -46,6 +46,8 @@
 mod default_weights;
 
 pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;
+pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;
+pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;
 
 // Structs
 // #region
@@ -116,6 +118,7 @@
     pub offchain_schema: Vec<u8>,
     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<u8>, //
     pub const_on_chain_schema: Vec<u8>, //
 }
@@ -171,6 +174,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: 10_000_000, 
+            token_limit: u32::max_value(),
+            sponsored_data_size: u32::max_value(), 
+            sponsor_transfer_timeout: 14400 }
+    }
+}
+
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 pub struct ChainLimits {
@@ -328,7 +352,13 @@
         /// 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,
+        /// Collection limit bounds per collection exceeded
+        CollectionLimitBoundsExceeded
 	}
 }
 
@@ -544,6 +574,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
@@ -1022,9 +1053,12 @@
         pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {
 
             let sender = ensure_signed(origin)?;
+            let target_collection = <Collection<T>>::get(collection_id);
+
+            // Limits check
+            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;
 
             // Transfer permissions check
-            let target_collection = <Collection<T>>::get(collection_id);
             ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||
                 Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
                 Error::<T>::NoPermission);
@@ -1135,11 +1169,15 @@
                 }
             }
 
-            // Transfer permissions check
             let target_collection = <Collection<T>>::get(collection_id);
-                ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
-                Error::<T>::NoPermission);
 
+            // Limits check
+            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;
+
+            // Transfer permissions check         
+            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
+            Error::<T>::NoPermission);
+
             if target_collection.access == AccessMode::WhiteList {
                 Self::check_white_list(collection_id, &sender)?;
                 Self::check_white_list(collection_id, &recipient)?;
@@ -1387,24 +1425,55 @@
             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)?;
-        //     <ContractOwner<T>>::insert(contract_address.clone(), owner);
-        //     Ok(())
-        // }
-    
+        #[weight = 0]
+        pub fn set_collection_limits(
+            origin,
+            collection_id: u32,
+            limits: CollectionLimits,
+        ) -> DispatchResult {
+            let sender = ensure_signed(origin)?;
+            Self::check_owner_permissions(collection_id, sender.clone())?;
+            let mut target_collection = <Collection<T>>::get(collection_id);
+            let chain_limits = ChainLimit::get();
+            let climits = target_collection.limits;
+
+            // collection bounds
+            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&
+                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  
+                Error::<T>::CollectionLimitBoundsExceeded);
+
+            // token_limit   check  prev
+            ensure!(climits.token_limit > limits.token_limit && 
+                limits.token_limit <= chain_limits.account_token_ownership_limit, 
+                Error::<T>::AccountTokenLimitExceeded);
+
+            target_collection.limits = limits;
+            <Collection<T>>::insert(collection_id, target_collection);
+
+            Ok(())
+        } 
     }
 }
 
 impl<T: Trait> Module<T> {
 
+    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {
+        
+        // check token limit and account token limit
+        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;
+        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);
+        
+        Ok(())
+    }
+
     fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {
 
+        // check token limit and account token limit
+        let total_items: u32 = ItemListIndex::get(collection_id);
+        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;
+        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);
+        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);
+
         if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {
             ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);
             Self::check_white_list(collection_id, owner)?;
@@ -1485,7 +1554,6 @@
                 Self::add_refungible_item(item)?;
             }
         };
-
 
         // call event
         Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));
@@ -2213,18 +2281,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::<Call<T>>::is_sub_type(call) {
-            Some(Call::create_item(collection_id, _properties, _owner)) => {
-                <Collection<T>>::get(collection_id).sponsor
+            Some(Call::create_item(collection_id, _owner, _properties)) => {
+
+                // check free create limit
+                if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)
+                {
+                    <Collection<T>>::get(collection_id).sponsor
+                } else {
+                    T::AccountId::default()
+                }
             }
             Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {
+                
+                let _collection_limits = <Collection<T>>::get(collection_id).limits;
                 let _collection_mode = <Collection<T>>::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 = <NftTransferBasket<T>>::get(collection_id, _item_id);
                         let block_number = <system::Module<T>>::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 {
                             <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);
                             true
@@ -2234,12 +2319,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 = <FungibleTransferBasket<T>>::get(collection_id, _item_id);
                         let block_number = <system::Module<T>>::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() });
@@ -2256,9 +2349,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 = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);
                         let block_number = <system::Module<T>>::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 {
                             <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);
                             true
modifiedpallets/nft/src/tests.rsdiffbeforeafterboth
--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -3,7 +3,7 @@
 use crate::mock::*;
 use crate::{AccessMode, ApprovePermissions, CollectionMode,
     Ownership, ChainLimits, CreateItemData, CreateNftData, CreateFungibleData, CreateReFungibleData,
-    CollectionId, TokenId, MAX_DECIMAL_POINTS}; //Err
+    CollectionId, TokenId, MAX_DECIMAL_POINTS};
 use frame_support::{assert_noop, assert_ok};
 use frame_system::{ RawOrigin };
 
modifiedruntime_types.jsondiffbeforeafterboth
before · runtime_types.json
1{2    "Schedule": {3      "version": "u32",4      "put_code_per_byte_cost": "Gas",5      "grow_mem_cost": "Gas",6      "regular_op_cost": "Gas",7      "return_data_per_byte_cost": "Gas",8      "event_data_per_byte_cost": "Gas",9      "event_per_topic_cost": "Gas",10      "event_base_cost": "Gas",11      "call_base_cost": "Gas",12      "instantiate_base_cost": "Gas",13      "dispatch_base_cost": "Gas",14      "sandbox_data_read_cost": "Gas",15      "sandbox_data_write_cost": "Gas",16      "transfer_cost": "Gas",17      "instantiate_cost": "Gas",18      "max_event_topics": "u32",19      "max_stack_height": "u32",20      "max_memory_pages": "u32",21      "max_table_size": "u32",22      "enable_println": "bool",23      "max_subject_len": "u32"24    },25    "AccessMode": {26      "_enum": [27        "Normal",28        "WhiteList"29      ]30    },31    "DecimalPoints": "u8",32    "CollectionMode": {33      "_enum": {34        "Invalid": null,35        "NFT": null,36        "Fungible": "DecimalPoints",37        "ReFungible": "DecimalPoints"38      }39    },40    "Ownership": {41      "Owner": "AccountId",42      "Fraction": "u128"43    },44    "FungibleItemType": {45      "Collection": "CollectionId",46      "Owner": "AccountId",47      "Value": "u128"48    },49    "ReFungibleItemType": {50      "Collection": "CollectionId",51      "Owner": "Vec<Ownership>",52      "Data": "Vec<u8>"53    },54    "NftItemType": {55      "Collection": "CollectionId",56      "Owner": "AccountId",57      "ConstData": "Vec<u8>",58      "VariableData": "Vec<u8>"59    },60    "Ownership": {61      "owner": "AccountId",62      "fraction": "u128"63    },64    "ReFungibleItemType": {65      "Collection": "CollectionId",66      "Owner": "Vec<Ownership<AccountId>>",67      "ConstData": "Vec<u8>",68      "VariableData": "Vec<u8>"69    },70    "CollectionType": {71      "Owner": "AccountId",72      "Mode": "CollectionMode",73      "Access": "AccessMode",74      "DecimalPoints": "DecimalPoints",75      "Name": "Vec<u16>",76      "Description": "Vec<u16>",77      "TokenPrefix": "Vec<u8>",78      "MintMode": "bool",79      "OffchainSchema": "Vec<u8>",80      "Sponsor": "AccountId",81      "UnconfirmedSponsor": "AccountId",82      "VariableOnChainSchema": "Vec<u8>",83      "ConstOnChainSchema": "Vec<u8>"84    },85    "ApprovePermissions": {86      "Approved": "AccountId",87      "Amount": "u128"88    },89    "RawData": "Vec<u8>",90    "Address": "AccountId",91    "LookupSource": "AccountId",92    "Weight": "u64",93    "CreateNftData": {94      "const_data": "Vec<u8>",95      "variable_data": "Vec<u8>" 96    },97    "CreateFungibleData": {},98    "CreateReFungibleData": {99      "const_data": "Vec<u8>",100      "variable_data": "Vec<u8>" 101    },102    "CreateItemData": {103      "_enum": {104        "NFT": "CreateNftData",105        "Fungible": "CreateFungibleData",106        "ReFungible": "CreateReFungibleData"107      }108    },109    "CollectionId": "u32",110    "TokenId": "u32",111    "BasketItem": {112      "Address": "AccountId",113      "start_block": "BlockNumber"114    },115    "ChainLimits": {116      "collection_numbers_limit": "u32",117      "account_token_ownership_limit": "u32",118      "collections_admins_limit": "u64",119      "custom_data_limit": "u32",120      "nft_sponsor_transfer_timeout": "u32",121      "fungible_sponsor_transfer_timeout": "u32",122      "refungible_sponsor_transfer_timeout": "u32"123    }124  }125  
after · runtime_types.json
1{2    "Schedule": {3      "version": "u32",4      "put_code_per_byte_cost": "Gas",5      "grow_mem_cost": "Gas",6      "regular_op_cost": "Gas",7      "return_data_per_byte_cost": "Gas",8      "event_data_per_byte_cost": "Gas",9      "event_per_topic_cost": "Gas",10      "event_base_cost": "Gas",11      "call_base_cost": "Gas",12      "instantiate_base_cost": "Gas",13      "dispatch_base_cost": "Gas",14      "sandbox_data_read_cost": "Gas",15      "sandbox_data_write_cost": "Gas",16      "transfer_cost": "Gas",17      "instantiate_cost": "Gas",18      "max_event_topics": "u32",19      "max_stack_height": "u32",20      "max_memory_pages": "u32",21      "max_table_size": "u32",22      "enable_println": "bool",23      "max_subject_len": "u32"24    },25    "AccessMode": {26      "_enum": [27        "Normal",28        "WhiteList"29      ]30    },31    "DecimalPoints": "u8",32    "CollectionMode": {33      "_enum": {34        "Invalid": null,35        "NFT": null,36        "Fungible": "DecimalPoints",37        "ReFungible": "DecimalPoints"38      }39    },40    "Ownership": {41      "Owner": "AccountId",42      "Fraction": "u128"43    },44    "FungibleItemType": {45      "Collection": "CollectionId",46      "Owner": "AccountId",47      "Value": "u128"48    },49    "NftItemType": {50      "Collection": "CollectionId",51      "Owner": "AccountId",52      "ConstData": "Vec<u8>",53      "VariableData": "Vec<u8>"54    },55    "ReFungibleItemType": {56      "Collection": "CollectionId",57      "Owner": "Vec<Ownership<AccountId>>",58      "ConstData": "Vec<u8>",59      "VariableData": "Vec<u8>"60    },61    "CollectionType": {62      "Owner": "AccountId",63      "Mode": "CollectionMode",64      "Access": "AccessMode",65      "DecimalPoints": "DecimalPoints",66      "Name": "Vec<u16>",67      "Description": "Vec<u16>",68      "TokenPrefix": "Vec<u8>",69      "MintMode": "bool",70      "OffchainSchema": "Vec<u8>",71      "Sponsor": "AccountId",72      "UnconfirmedSponsor": "AccountId",73      "Limits": "CollectionLimits",74      "VariableOnChainSchema": "Vec<u8>",75      "ConstOnChainSchema": "Vec<u8>"76    },77    "ApprovePermissions": {78      "Approved": "AccountId",79      "Amount": "u128"80    },81    "RawData": "Vec<u8>",82    "Address": "AccountId",83    "LookupSource": "AccountId",84    "Weight": "u64",85    "CreateNftData": {86      "const_data": "Vec<u8>",87      "variable_data": "Vec<u8>" 88    },89    "CreateFungibleData": {},90    "CreateReFungibleData": {91      "const_data": "Vec<u8>",92      "variable_data": "Vec<u8>" 93    },94    "CreateItemData": {95      "_enum": {96        "NFT": "CreateNftData",97        "Fungible": "CreateFungibleData",98        "ReFungible": "CreateReFungibleData"99      }100    },101    "CollectionId": "u32",102    "TokenId": "u32",103    "BasketItem": {104      "Address": "AccountId",105      "start_block": "BlockNumber"106    },107    "ChainLimits": {108      "collection_numbers_limit": "u32",109      "account_token_ownership_limit": "u32",110      "collections_admins_limit": "u64",111      "custom_data_limit": "u32",112      "nft_sponsor_timeout": "u32",113      "fungible_sponsor_timeout": "u32",114      "refungible_sponsor_timeout": "u32"115    },116    "CollectionLimits": {117      "AccountTokenOwnershipLimit": "u32",118      "SponsoredMintSize": "u32",119      "TokenLimit": "u32",120      "SponsorTimeout": "u32"121    }122  }123