git.delta.rocks / unique-network / refs/commits / 8ccb2682a57d

difftreelog

refactor make collection limits fields optional

Yaroslav Bolyukin2021-11-04parent: #579977f.patch.diff
in: master

8 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -105,10 +105,10 @@
 		Ok(())
 	}
 	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {
-		Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)
+		Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)
 	}
 	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {
-		Ok(self.limits.owner_can_transfer && self.is_owner_or_admin(user)?)
+		Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)
 	}
 	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {
 		self.consume_sload()?;
@@ -405,9 +405,10 @@
 		collection: CollectionHandle<T>,
 		sender: &T::CrossAccountId,
 	) -> DispatchResult {
-		if !collection.limits.owner_can_destroy {
-			fail!(Error::<T>::NoPermission);
-		}
+		ensure!(
+			collection.limits.owner_can_destroy(),
+			<Error<T>>::NoPermission,
+		);
 		collection.check_is_owner(&sender)?;
 
 		let destroyed_collections = <DestroyedCollectionCount<T>>::get()
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -157,8 +157,8 @@
 		amount: u128,
 	) -> DispatchResult {
 		ensure!(
-			collection.transfers_enabled,
-			<CommonError<T>>::TransferNotAllowed
+			collection.limits.transfers_enabled(),
+			<CommonError<T>>::TransferNotAllowed,
 		);
 
 		if collection.access == AccessMode::WhiteList {
modifiedpallets/nft/src/eth/sponsoring.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/sponsoring.rs
+++ b/pallets/nft/src/eth/sponsoring.rs
@@ -43,11 +43,8 @@
 					let token_id: u32 = token_id.try_into().map_err(|_| AnyError)?;
 					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 					let collection_limits = &collection.limits;
-					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
-						collection_limits.sponsor_transfer_timeout
-					} else {
-						NFT_SPONSOR_TRANSFER_TIMEOUT
-					};
+					let limit =
+						collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
 
 					let mut sponsor = true;
 					if <NftTransferBasket<T>>::contains_key(collection_id, token_id) {
@@ -74,11 +71,8 @@
 				UniqueFungibleCall::ERC20(ERC20Call::Transfer { .. }) => {
 					let who = T::CrossAccountId::from_eth(*caller);
 					let collection_limits = &collection.limits;
-					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
-						collection_limits.sponsor_transfer_timeout
-					} else {
-						FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
-					};
+					let limit = collection_limits
+						.sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
 
 					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 					let mut sponsored = true;
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24		IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32	pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,40	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,41	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,42};43use pallet_common::{44	account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,45	Error as CommonError, CommonWeightInfo, Allowlist,46};47use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};48use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};49use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5051#[cfg(test)]52mod mock;5354#[cfg(test)]55mod tests;5657mod eth;58mod sponsorship;59pub use sponsorship::NftSponsorshipHandler;60pub use eth::sponsoring::NftEthSponsorshipHandler;6162pub use eth::NftErcSupport;6364pub mod common;65use common::CommonWeights;66pub mod dispatch;67use dispatch::dispatch_call;6869#[cfg(feature = "runtime-benchmarks")]70mod benchmarking;71pub mod weights;72use weights::WeightInfo;7374decl_error! {75	/// Error for non-fungible-token module.76	pub enum Error for Module<T: Config> {77		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.78		CollectionDecimalPointLimitExceeded,79		/// This address is not set as sponsor, use setCollectionSponsor first.80		ConfirmUnsetSponsorFail,81		/// Length of items properties must be greater than 0.82		EmptyArgument,83		/// Collection limit bounds per collection exceeded84		CollectionLimitBoundsExceeded,85		/// Tried to enable permissions which are only permitted to be disabled86		OwnerPermissionsCantBeReverted,87	}88}89pub trait Config:90	system::Config91	+ pallet_evm_coder_substrate::Config92	+ pallet_common::Config93	+ pallet_nonfungible::Config94	+ pallet_refungible::Config95	+ pallet_fungible::Config96	+ Sized97	+ TypeInfo98{99	/// Weight information for extrinsics in this pallet.100	type WeightInfo: WeightInfo;101}102103type SelfWeightOf<T> = <T as Config>::WeightInfo;104105// # Used definitions106//107// ## User control levels108//109// chain-controlled - key is uncontrolled by user110//                    i.e autoincrementing index111//                    can use non-cryptographic hash112// real - key is controlled by user113//        but it is hard to generate enough colliding values, i.e owner of signed txs114//        can use non-cryptographic hash115// controlled - key is completly controlled by users116//              i.e maps with mutable keys117//              should use cryptographic hash118//119// ## User control level downgrade reasons120//121// ?1 - chain-controlled -> controlled122//      collections/tokens can be destroyed, resulting in massive holes123// ?2 - chain-controlled -> controlled124//      same as ?1, but can be only added, resulting in easier exploitation125// ?3 - real -> controlled126//      no confirmation required, so addresses can be easily generated127decl_storage! {128	trait Store for Module<T: Config> as Nft {129130		//#region Private members131		/// Used for migrations132		ChainVersion: u64;133		//#endregion134135		//#region Tokens transfer rate limit baskets136		/// (Collection id (controlled?2), who created (real))137		/// TODO: Off chain worker should remove from this map when collection gets removed138		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;139		/// Collection id (controlled?2), token id (controlled?2)140		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;141		/// Collection id (controlled?2), owning user (real)142		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;143		/// Collection id (controlled?2), token id (controlled?2)144		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;145		//#endregion146147		/// Variable metadata sponsoring148		/// Collection id (controlled?2), token id (controlled?2)149		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;150	}151}152153decl_module! {154	pub struct Module<T: Config> for enum Call155	where156		origin: T::Origin157	{158		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;159		type Error = Error<T>;160161		fn on_initialize(_now: T::BlockNumber) -> Weight {162			0163		}164165		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.166		///167		/// # Permissions168		///169		/// * Anyone.170		///171		/// # Arguments172		///173		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.174		///175		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.176		///177		/// * token_prefix: UTF-8 string with token prefix.178		///179		/// * mode: [CollectionMode] collection type and type dependent data.180		// returns collection ID181		#[weight = <SelfWeightOf<T>>::create_collection()]182		#[transactional]183		pub fn create_collection(origin,184								 collection_name: Vec<u16>,185								 collection_description: Vec<u16>,186								 token_prefix: Vec<u8>,187								 mode: CollectionMode) -> DispatchResult {188189			// Anyone can create a collection190			let who = ensure_signed(origin)?;191192			let limits = CollectionLimits::<T::BlockNumber> {193				sponsored_data_size: CUSTOM_DATA_LIMIT,194				..Default::default()195			};196197			// Create new collection198			let new_collection = Collection::<T> {199				owner: who.clone(),200				name: collection_name,201				mode: mode.clone(),202				mint_mode: false,203				access: AccessMode::Normal,204				description: collection_description,205				token_prefix,206				offchain_schema: Vec::new(),207				schema_version: SchemaVersion::ImageURL,208				sponsorship: SponsorshipState::Disabled,209				variable_on_chain_schema: Vec::new(),210				const_on_chain_schema: Vec::new(),211				limits,212				transfers_enabled: true,213				meta_update_permission: Default::default(),214			};215216			let _id = match mode {217				CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},218				CollectionMode::Fungible(decimal_points) => {219					// check params220					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);221					PalletFungible::init_collection(new_collection)?222				}223				CollectionMode::ReFungible => {224					PalletRefungible::init_collection(new_collection)?225				}226			};227228			Ok(())229		}230231		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.232		///233		/// # Permissions234		///235		/// * Collection Owner.236		///237		/// # Arguments238		///239		/// * collection_id: collection to destroy.240		#[weight = <SelfWeightOf<T>>::destroy_collection()]241		#[transactional]242		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {243			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);244245			let collection = <CollectionHandle<T>>::try_get(collection_id)?;246			collection.check_is_owner(&sender)?;247248			// =========249250			match collection.mode {251				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,252				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,253				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,254			}255256			<NftTransferBasket<T>>::remove_prefix(collection_id, None);257			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);258			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);259260			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);261262			Ok(())263		}264265		/// Add an address to white list.266		///267		/// # Permissions268		///269		/// * Collection Owner270		/// * Collection Admin271		///272		/// # Arguments273		///274		/// * collection_id.275		///276		/// * address.277		#[weight = <SelfWeightOf<T>>::add_to_white_list()]278		#[transactional]279		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{280281			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);282			let collection = <CollectionHandle<T>>::try_get(collection_id)?;283284			<PalletCommon<T>>::toggle_allowlist(285				&collection,286				&sender,287				&address,288				true,289			)?;290291			Ok(())292		}293294		/// Remove an address from white list.295		///296		/// # Permissions297		///298		/// * Collection Owner299		/// * Collection Admin300		///301		/// # Arguments302		///303		/// * collection_id.304		///305		/// * address.306		#[weight = <SelfWeightOf<T>>::remove_from_white_list()]307		#[transactional]308		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{309310			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);311			let collection = <CollectionHandle<T>>::try_get(collection_id)?;312313			<PalletCommon<T>>::toggle_allowlist(314				&collection,315				&sender,316				&address,317				false,318			)?;319320			Ok(())321		}322323		/// Toggle between normal and white list access for the methods with access for `Anyone`.324		///325		/// # Permissions326		///327		/// * Collection Owner.328		///329		/// # Arguments330		///331		/// * collection_id.332		///333		/// * mode: [AccessMode]334		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]335		#[transactional]336		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult337		{338			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);339340			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;341			target_collection.check_is_owner(&sender)?;342343			target_collection.access = mode;344			target_collection.save()345		}346347		/// Allows Anyone to create tokens if:348		/// * White List is enabled, and349		/// * Address is added to white list, and350		/// * This method was called with True parameter351		///352		/// # Permissions353		/// * Collection Owner354		///355		/// # Arguments356		///357		/// * collection_id.358		///359		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.360		#[weight = <SelfWeightOf<T>>::set_mint_permission()]361		#[transactional]362		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult363		{364			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365366			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;367			target_collection.check_is_owner(&sender)?;368369			target_collection.mint_mode = mint_permission;370			target_collection.save()371		}372373		/// Change the owner of the collection.374		///375		/// # Permissions376		///377		/// * Collection Owner.378		///379		/// # Arguments380		///381		/// * collection_id.382		///383		/// * new_owner.384		#[weight = <SelfWeightOf<T>>::change_collection_owner()]385		#[transactional]386		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {387388			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);389390			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;391			target_collection.check_is_owner(&sender)?;392393			target_collection.owner = new_owner;394			target_collection.save()395		}396397		/// Adds an admin of the Collection.398		/// 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.399		///400		/// # Permissions401		///402		/// * Collection Owner.403		/// * Collection Admin.404		///405		/// # Arguments406		///407		/// * collection_id: ID of the Collection to add admin for.408		///409		/// * new_admin_id: Address of new admin to add.410		#[weight = <SelfWeightOf<T>>::add_collection_admin()]411		#[transactional]412		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {413			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);414415			let collection = <CollectionHandle<T>>::try_get(collection_id)?;416			collection.check_is_owner_or_admin(&sender)?;417418			<IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);419			Ok(())420		}421422		/// 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.423		///424		/// # Permissions425		///426		/// * Collection Owner.427		/// * Collection Admin.428		///429		/// # Arguments430		///431		/// * collection_id: ID of the Collection to remove admin for.432		///433		/// * account_id: Address of admin to remove.434		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]435		#[transactional]436		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {437			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);438439			let collection = <CollectionHandle<T>>::try_get(collection_id)?;440			collection.check_is_owner_or_admin(&sender)?;441442			<IsAdmin<T>>::remove((collection_id, account_id.as_sub()));443			Ok(())444		}445446		/// # Permissions447		///448		/// * Collection Owner449		///450		/// # Arguments451		///452		/// * collection_id.453		///454		/// * new_sponsor.455		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]456		#[transactional]457		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {458			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459460			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;461			target_collection.check_is_owner_or_admin(&sender)?;462463			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);464			target_collection.save()465		}466467		/// # Permissions468		///469		/// * Sponsor.470		///471		/// # Arguments472		///473		/// * collection_id.474		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]475		#[transactional]476		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {477			let sender = ensure_signed(origin)?;478479			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;480			ensure!(481				target_collection.sponsorship.pending_sponsor() == Some(&sender),482				Error::<T>::ConfirmUnsetSponsorFail483			);484485			target_collection.sponsorship = SponsorshipState::Confirmed(sender);486			target_collection.save()487		}488489		/// Switch back to pay-per-own-transaction model.490		///491		/// # Permissions492		///493		/// * Collection owner.494		///495		/// # Arguments496		///497		/// * collection_id.498		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]499		#[transactional]500		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {501			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);502503			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;504			target_collection.check_is_owner(&sender)?;505506			target_collection.sponsorship = SponsorshipState::Disabled;507			target_collection.save()508		}509510		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.511		///512		/// # Permissions513		///514		/// * Collection Owner.515		/// * Collection Admin.516		/// * Anyone if517		///     * White List is enabled, and518		///     * Address is added to white list, and519		///     * MintPermission is enabled (see SetMintPermission method)520		///521		/// # Arguments522		///523		/// * collection_id: ID of the collection.524		///525		/// * owner: Address, initial owner of the NFT.526		///527		/// * data: Token data to store on chain.528		#[weight = <CommonWeights<T>>::create_item()]529		#[transactional]530		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {531			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);532533			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))534		}535536		/// This method creates multiple items in a collection created with CreateCollection method.537		///538		/// # Permissions539		///540		/// * Collection Owner.541		/// * Collection Admin.542		/// * Anyone if543		///     * White List is enabled, and544		///     * Address is added to white list, and545		///     * MintPermission is enabled (see SetMintPermission method)546		///547		/// # Arguments548		///549		/// * collection_id: ID of the collection.550		///551		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].552		///553		/// * owner: Address, initial owner of the NFT.554		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]555		#[transactional]556		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {557			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);558			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);559560			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))561		}562563		// TODO! transaction weight564565		/// Set transfers_enabled value for particular collection566		///567		/// # Permissions568		///569		/// * Collection Owner.570		///571		/// # Arguments572		///573		/// * collection_id: ID of the collection.574		///575		/// * value: New flag value.576		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]577		#[transactional]578		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {579			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);580			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581			target_collection.check_is_owner(&sender)?;582583			// =========584585			target_collection.transfers_enabled = value;586			target_collection.save()587		}588589		/// Destroys a concrete instance of NFT.590		///591		/// # Permissions592		///593		/// * Collection Owner.594		/// * Collection Admin.595		/// * Current NFT Owner.596		///597		/// # Arguments598		///599		/// * collection_id: ID of the collection.600		///601		/// * item_id: ID of NFT to burn.602		#[weight = <CommonWeights<T>>::burn_item()]603		#[transactional]604		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {605			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);606607			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))608		}609610		/// Destroys a concrete instance of NFT on behalf of the owner611		/// See also: [`approve`]612		///613		/// # Permissions614		///615		/// * Collection Owner.616		/// * Collection Admin.617		/// * Current NFT Owner.618		///619		/// # Arguments620		///621		/// * collection_id: ID of the collection.622		///623		/// * item_id: ID of NFT to burn.624		///625		/// * from: owner of item626		#[weight = <CommonWeights<T>>::burn_from()]627		#[transactional]628		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {629			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630631			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))632		}633634		/// Change ownership of the token.635		///636		/// # Permissions637		///638		/// * Collection Owner639		/// * Collection Admin640		/// * Current NFT owner641		///642		/// # Arguments643		///644		/// * recipient: Address of token recipient.645		///646		/// * collection_id.647		///648		/// * item_id: ID of the item649		///     * Non-Fungible Mode: Required.650		///     * Fungible Mode: Ignored.651		///     * Re-Fungible Mode: Required.652		///653		/// * value: Amount to transfer.654		///     * Non-Fungible Mode: Ignored655		///     * Fungible Mode: Must specify transferred amount656		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)657		#[weight = <CommonWeights<T>>::transfer()]658		#[transactional]659		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {660			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);661662			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))663		}664665		/// Set, change, or remove approved address to transfer the ownership of the NFT.666		///667		/// # Permissions668		///669		/// * Collection Owner670		/// * Collection Admin671		/// * Current NFT owner672		///673		/// # Arguments674		///675		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).676		///677		/// * collection_id.678		///679		/// * item_id: ID of the item.680		#[weight = <CommonWeights<T>>::approve()]681		#[transactional]682		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {683			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);684685			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))686		}687688		/// 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.689		///690		/// # Permissions691		/// * Collection Owner692		/// * Collection Admin693		/// * Current NFT owner694		/// * Address approved by current NFT owner695		///696		/// # Arguments697		///698		/// * from: Address that owns token.699		///700		/// * recipient: Address of token recipient.701		///702		/// * collection_id.703		///704		/// * item_id: ID of the item.705		///706		/// * value: Amount to transfer.707		#[weight = <CommonWeights<T>>::transfer_from()]708		#[transactional]709		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {710			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);711712			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))713		}714715		/// Set off-chain data schema.716		///717		/// # Permissions718		///719		/// * Collection Owner720		/// * Collection Admin721		///722		/// # Arguments723		///724		/// * collection_id.725		///726		/// * schema: String representing the offchain data schema.727		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]728		#[transactional]729		pub fn set_variable_meta_data (730			origin,731			collection_id: CollectionId,732			item_id: TokenId,733			data: Vec<u8>734		) -> DispatchResultWithPostInfo {735			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);736737			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))738		}739740		/// Set meta_update_permission value for particular collection741		///742		/// # Permissions743		///744		/// * Collection Owner.745		///746		/// # Arguments747		///748		/// * collection_id: ID of the collection.749		///750		/// * value: New flag value.751		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]752		#[transactional]753		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {754			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);755			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;756757			ensure!(758				target_collection.meta_update_permission != MetaUpdatePermission::None,759				<CommonError<T>>::MetadataFlagFrozen,760			);761			target_collection.check_is_owner(&sender)?;762763			target_collection.meta_update_permission = value;764765			target_collection.save()766		}767768		/// Set schema standard769		/// ImageURL770		/// Unique771		///772		/// # Permissions773		///774		/// * Collection Owner775		/// * Collection Admin776		///777		/// # Arguments778		///779		/// * collection_id.780		///781		/// * schema: SchemaVersion: enum782		#[weight = <SelfWeightOf<T>>::set_schema_version()]783		#[transactional]784		pub fn set_schema_version(785			origin,786			collection_id: CollectionId,787			version: SchemaVersion788		) -> DispatchResult {789			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);790			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;791			target_collection.check_is_owner_or_admin(&sender)?;792			target_collection.schema_version = version;793			target_collection.save()794		}795796		/// Set off-chain data schema.797		///798		/// # Permissions799		///800		/// * Collection Owner801		/// * Collection Admin802		///803		/// # Arguments804		///805		/// * collection_id.806		///807		/// * schema: String representing the offchain data schema.808		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]809		#[transactional]810		pub fn set_offchain_schema(811			origin,812			collection_id: CollectionId,813			schema: Vec<u8>814		) -> DispatchResult {815			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);816			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;817			target_collection.check_is_owner_or_admin(&sender)?;818819			// check schema limit820			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");821822			target_collection.offchain_schema = schema;823			target_collection.save()824		}825826		/// Set const on-chain data schema.827		///828		/// # Permissions829		///830		/// * Collection Owner831		/// * Collection Admin832		///833		/// # Arguments834		///835		/// * collection_id.836		///837		/// * schema: String representing the const on-chain data schema.838		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]839		#[transactional]840		pub fn set_const_on_chain_schema (841			origin,842			collection_id: CollectionId,843			schema: Vec<u8>844		) -> DispatchResult {845			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);846			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;847			target_collection.check_is_owner_or_admin(&sender)?;848849			// check schema limit850			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");851852			target_collection.const_on_chain_schema = schema;853			target_collection.save()854		}855856		/// Set variable on-chain data schema.857		///858		/// # Permissions859		///860		/// * Collection Owner861		/// * Collection Admin862		///863		/// # Arguments864		///865		/// * collection_id.866		///867		/// * schema: String representing the variable on-chain data schema.868		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]869		#[transactional]870		pub fn set_variable_on_chain_schema (871			origin,872			collection_id: CollectionId,873			schema: Vec<u8>874		) -> DispatchResult {875			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);876			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;877			target_collection.check_is_owner_or_admin(&sender)?;878879			// check schema limit880			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");881882			target_collection.variable_on_chain_schema = schema;883			target_collection.save()884		}885886		#[weight = <SelfWeightOf<T>>::set_collection_limits()]887		#[transactional]888		pub fn set_collection_limits(889			origin,890			collection_id: CollectionId,891			new_limits: CollectionLimits<T::BlockNumber>,892		) -> DispatchResult {893			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);894			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;895			target_collection.check_is_owner(&sender)?;896			let old_limits = &target_collection.limits;897898			// collection bounds899			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&900				new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&901				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,902				Error::<T>::CollectionLimitBoundsExceeded);903904			// token_limit   check  prev905			ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);906			ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);907908			ensure!(909				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&910				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),911				Error::<T>::OwnerPermissionsCantBeReverted,912			);913914			target_collection.limits = new_limits;915916			target_collection.save()917		}918	}919}920921// TODO: limit returned entries?922impl<T: Config> Pallet<T> {923	pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {924		<IsAdmin<T>>::iter_prefix((collection,))925			.map(|(a, _)| a)926			.collect()927	}928	pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {929		<Allowlist<T>>::iter_prefix((collection,))930			.map(|(a, _)| a)931			.collect()932	}933}
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced, Randomness,24		IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32	pallet_prelude::DispatchResultWithPostInfo,33};34use scale_info::TypeInfo;35use frame_system::{self as system, ensure_signed};36use sp_runtime::{sp_std::prelude::Vec};37use nft_data_structs::{38	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,39	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,40	OFFCHAIN_SCHEMA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,41	NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,42	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,43};44use pallet_common::{45	account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,46	Error as CommonError, CommonWeightInfo, Allowlist,47};48use pallet_refungible::{Pallet as PalletRefungible, RefungibleHandle};49use pallet_fungible::{Pallet as PalletFungible, FungibleHandle};50use pallet_nonfungible::{Pallet as PalletNonfungible, NonfungibleHandle};5152#[cfg(test)]53mod mock;5455#[cfg(test)]56mod tests;5758mod eth;59mod sponsorship;60pub use sponsorship::NftSponsorshipHandler;61pub use eth::sponsoring::NftEthSponsorshipHandler;6263pub use eth::NftErcSupport;6465pub mod common;66use common::CommonWeights;67pub mod dispatch;68use dispatch::dispatch_call;6970#[cfg(feature = "runtime-benchmarks")]71mod benchmarking;72pub mod weights;73use weights::WeightInfo;7475decl_error! {76	/// Error for non-fungible-token module.77	pub enum Error for Module<T: Config> {78		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.79		CollectionDecimalPointLimitExceeded,80		/// This address is not set as sponsor, use setCollectionSponsor first.81		ConfirmUnsetSponsorFail,82		/// Length of items properties must be greater than 0.83		EmptyArgument,84		/// Collection limit bounds per collection exceeded85		CollectionLimitBoundsExceeded,86		/// Tried to enable permissions which are only permitted to be disabled87		OwnerPermissionsCantBeReverted,88	}89}90pub trait Config:91	system::Config92	+ pallet_evm_coder_substrate::Config93	+ pallet_common::Config94	+ pallet_nonfungible::Config95	+ pallet_refungible::Config96	+ pallet_fungible::Config97	+ Sized98	+ TypeInfo99{100	/// Weight information for extrinsics in this pallet.101	type WeightInfo: WeightInfo;102}103104type SelfWeightOf<T> = <T as Config>::WeightInfo;105106// # Used definitions107//108// ## User control levels109//110// chain-controlled - key is uncontrolled by user111//                    i.e autoincrementing index112//                    can use non-cryptographic hash113// real - key is controlled by user114//        but it is hard to generate enough colliding values, i.e owner of signed txs115//        can use non-cryptographic hash116// controlled - key is completly controlled by users117//              i.e maps with mutable keys118//              should use cryptographic hash119//120// ## User control level downgrade reasons121//122// ?1 - chain-controlled -> controlled123//      collections/tokens can be destroyed, resulting in massive holes124// ?2 - chain-controlled -> controlled125//      same as ?1, but can be only added, resulting in easier exploitation126// ?3 - real -> controlled127//      no confirmation required, so addresses can be easily generated128decl_storage! {129	trait Store for Module<T: Config> as Nft {130131		//#region Private members132		/// Used for migrations133		ChainVersion: u64;134		//#endregion135136		//#region Tokens transfer rate limit baskets137		/// (Collection id (controlled?2), who created (real))138		/// TODO: Off chain worker should remove from this map when collection gets removed139		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;140		/// Collection id (controlled?2), token id (controlled?2)141		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;142		/// Collection id (controlled?2), owning user (real)143		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;144		/// Collection id (controlled?2), token id (controlled?2)145		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;146		//#endregion147148		/// Variable metadata sponsoring149		/// Collection id (controlled?2), token id (controlled?2)150		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;151	}152}153154decl_module! {155	pub struct Module<T: Config> for enum Call156	where157		origin: T::Origin158	{159		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;160		type Error = Error<T>;161162		fn on_initialize(_now: T::BlockNumber) -> Weight {163			0164		}165166		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.167		///168		/// # Permissions169		///170		/// * Anyone.171		///172		/// # Arguments173		///174		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.175		///176		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.177		///178		/// * token_prefix: UTF-8 string with token prefix.179		///180		/// * mode: [CollectionMode] collection type and type dependent data.181		// returns collection ID182		#[weight = <SelfWeightOf<T>>::create_collection()]183		#[transactional]184		pub fn create_collection(origin,185								 collection_name: Vec<u16>,186								 collection_description: Vec<u16>,187								 token_prefix: Vec<u8>,188								 mode: CollectionMode) -> DispatchResult {189190			// Anyone can create a collection191			let who = ensure_signed(origin)?;192193			// Create new collection194			let new_collection = Collection::<T> {195				owner: who.clone(),196				name: collection_name,197				mode: mode.clone(),198				mint_mode: false,199				access: AccessMode::Normal,200				description: collection_description,201				token_prefix,202				offchain_schema: Vec::new(),203				schema_version: SchemaVersion::ImageURL,204				sponsorship: SponsorshipState::Disabled,205				variable_on_chain_schema: Vec::new(),206				const_on_chain_schema: Vec::new(),207				limits: Default::default(),208				meta_update_permission: Default::default(),209			};210211			let _id = match mode {212				CollectionMode::NFT => {PalletNonfungible::init_collection(new_collection)?},213				CollectionMode::Fungible(decimal_points) => {214					// check params215					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);216					PalletFungible::init_collection(new_collection)?217				}218				CollectionMode::ReFungible => {219					PalletRefungible::init_collection(new_collection)?220				}221			};222223			Ok(())224		}225226		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.227		///228		/// # Permissions229		///230		/// * Collection Owner.231		///232		/// # Arguments233		///234		/// * collection_id: collection to destroy.235		#[weight = <SelfWeightOf<T>>::destroy_collection()]236		#[transactional]237		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {238			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);239240			let collection = <CollectionHandle<T>>::try_get(collection_id)?;241			collection.check_is_owner(&sender)?;242243			// =========244245			match collection.mode {246				CollectionMode::ReFungible => PalletRefungible::destroy_collection(RefungibleHandle::cast(collection), &sender)?,247				CollectionMode::Fungible(_) => PalletFungible::destroy_collection(FungibleHandle::cast(collection), &sender)?,248				CollectionMode::NFT => PalletNonfungible::destroy_collection(NonfungibleHandle::cast(collection), &sender)?,249			}250251			<NftTransferBasket<T>>::remove_prefix(collection_id, None);252			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);253			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);254255			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);256257			Ok(())258		}259260		/// Add an address to white list.261		///262		/// # Permissions263		///264		/// * Collection Owner265		/// * Collection Admin266		///267		/// # Arguments268		///269		/// * collection_id.270		///271		/// * address.272		#[weight = <SelfWeightOf<T>>::add_to_white_list()]273		#[transactional]274		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{275276			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);277			let collection = <CollectionHandle<T>>::try_get(collection_id)?;278279			<PalletCommon<T>>::toggle_allowlist(280				&collection,281				&sender,282				&address,283				true,284			)?;285286			Ok(())287		}288289		/// Remove an address from white list.290		///291		/// # Permissions292		///293		/// * Collection Owner294		/// * Collection Admin295		///296		/// # Arguments297		///298		/// * collection_id.299		///300		/// * address.301		#[weight = <SelfWeightOf<T>>::remove_from_white_list()]302		#[transactional]303		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{304305			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);306			let collection = <CollectionHandle<T>>::try_get(collection_id)?;307308			<PalletCommon<T>>::toggle_allowlist(309				&collection,310				&sender,311				&address,312				false,313			)?;314315			Ok(())316		}317318		/// Toggle between normal and white list access for the methods with access for `Anyone`.319		///320		/// # Permissions321		///322		/// * Collection Owner.323		///324		/// # Arguments325		///326		/// * collection_id.327		///328		/// * mode: [AccessMode]329		#[weight = <SelfWeightOf<T>>::set_public_access_mode()]330		#[transactional]331		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult332		{333			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);334335			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;336			target_collection.check_is_owner(&sender)?;337338			target_collection.access = mode;339			target_collection.save()340		}341342		/// Allows Anyone to create tokens if:343		/// * White List is enabled, and344		/// * Address is added to white list, and345		/// * This method was called with True parameter346		///347		/// # Permissions348		/// * Collection Owner349		///350		/// # Arguments351		///352		/// * collection_id.353		///354		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.355		#[weight = <SelfWeightOf<T>>::set_mint_permission()]356		#[transactional]357		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult358		{359			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);360361			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;362			target_collection.check_is_owner(&sender)?;363364			target_collection.mint_mode = mint_permission;365			target_collection.save()366		}367368		/// Change the owner of the collection.369		///370		/// # Permissions371		///372		/// * Collection Owner.373		///374		/// # Arguments375		///376		/// * collection_id.377		///378		/// * new_owner.379		#[weight = <SelfWeightOf<T>>::change_collection_owner()]380		#[transactional]381		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {382383			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);384385			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;386			target_collection.check_is_owner(&sender)?;387388			target_collection.owner = new_owner;389			target_collection.save()390		}391392		/// Adds an admin of the Collection.393		/// 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.394		///395		/// # Permissions396		///397		/// * Collection Owner.398		/// * Collection Admin.399		///400		/// # Arguments401		///402		/// * collection_id: ID of the Collection to add admin for.403		///404		/// * new_admin_id: Address of new admin to add.405		#[weight = <SelfWeightOf<T>>::add_collection_admin()]406		#[transactional]407		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {408			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);409410			let collection = <CollectionHandle<T>>::try_get(collection_id)?;411			collection.check_is_owner_or_admin(&sender)?;412413			<IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);414			Ok(())415		}416417		/// 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.418		///419		/// # Permissions420		///421		/// * Collection Owner.422		/// * Collection Admin.423		///424		/// # Arguments425		///426		/// * collection_id: ID of the Collection to remove admin for.427		///428		/// * account_id: Address of admin to remove.429		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]430		#[transactional]431		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {432			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);433434			let collection = <CollectionHandle<T>>::try_get(collection_id)?;435			collection.check_is_owner_or_admin(&sender)?;436437			<IsAdmin<T>>::remove((collection_id, account_id.as_sub()));438			Ok(())439		}440441		/// # Permissions442		///443		/// * Collection Owner444		///445		/// # Arguments446		///447		/// * collection_id.448		///449		/// * new_sponsor.450		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]451		#[transactional]452		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {453			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);454455			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;456			target_collection.check_is_owner_or_admin(&sender)?;457458			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);459			target_collection.save()460		}461462		/// # Permissions463		///464		/// * Sponsor.465		///466		/// # Arguments467		///468		/// * collection_id.469		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]470		#[transactional]471		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {472			let sender = ensure_signed(origin)?;473474			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;475			ensure!(476				target_collection.sponsorship.pending_sponsor() == Some(&sender),477				Error::<T>::ConfirmUnsetSponsorFail478			);479480			target_collection.sponsorship = SponsorshipState::Confirmed(sender);481			target_collection.save()482		}483484		/// Switch back to pay-per-own-transaction model.485		///486		/// # Permissions487		///488		/// * Collection owner.489		///490		/// # Arguments491		///492		/// * collection_id.493		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]494		#[transactional]495		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {496			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);497498			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;499			target_collection.check_is_owner(&sender)?;500501			target_collection.sponsorship = SponsorshipState::Disabled;502			target_collection.save()503		}504505		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.506		///507		/// # Permissions508		///509		/// * Collection Owner.510		/// * Collection Admin.511		/// * Anyone if512		///     * White List is enabled, and513		///     * Address is added to white list, and514		///     * MintPermission is enabled (see SetMintPermission method)515		///516		/// # Arguments517		///518		/// * collection_id: ID of the collection.519		///520		/// * owner: Address, initial owner of the NFT.521		///522		/// * data: Token data to store on chain.523		#[weight = <CommonWeights<T>>::create_item()]524		#[transactional]525		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {526			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);527528			dispatch_call::<T, _>(collection_id, |d| d.create_item(sender, owner, data))529		}530531		/// This method creates multiple items in a collection created with CreateCollection method.532		///533		/// # Permissions534		///535		/// * Collection Owner.536		/// * Collection Admin.537		/// * Anyone if538		///     * White List is enabled, and539		///     * Address is added to white list, and540		///     * MintPermission is enabled (see SetMintPermission method)541		///542		/// # Arguments543		///544		/// * collection_id: ID of the collection.545		///546		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].547		///548		/// * owner: Address, initial owner of the NFT.549		#[weight = <CommonWeights<T>>::create_multiple_items(items_data.len() as u32)]550		#[transactional]551		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {552			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);553			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);554555			dispatch_call::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data))556		}557558		// TODO! transaction weight559560		/// Set transfers_enabled value for particular collection561		///562		/// # Permissions563		///564		/// * Collection Owner.565		///566		/// # Arguments567		///568		/// * collection_id: ID of the collection.569		///570		/// * value: New flag value.571		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]572		#[transactional]573		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {574			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);575			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;576			target_collection.check_is_owner(&sender)?;577578			// =========579580			target_collection.limits.transfers_enabled = Some(value);581			target_collection.save()582		}583584		/// Destroys a concrete instance of NFT.585		///586		/// # Permissions587		///588		/// * Collection Owner.589		/// * Collection Admin.590		/// * Current NFT Owner.591		///592		/// # Arguments593		///594		/// * collection_id: ID of the collection.595		///596		/// * item_id: ID of NFT to burn.597		#[weight = <CommonWeights<T>>::burn_item()]598		#[transactional]599		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {600			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);601602			dispatch_call::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))603		}604605		/// Destroys a concrete instance of NFT on behalf of the owner606		/// See also: [`approve`]607		///608		/// # Permissions609		///610		/// * Collection Owner.611		/// * Collection Admin.612		/// * Current NFT Owner.613		///614		/// # Arguments615		///616		/// * collection_id: ID of the collection.617		///618		/// * item_id: ID of NFT to burn.619		///620		/// * from: owner of item621		#[weight = <CommonWeights<T>>::burn_from()]622		#[transactional]623		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {624			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);625626			dispatch_call::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value))627		}628629		/// Change ownership of the token.630		///631		/// # Permissions632		///633		/// * Collection Owner634		/// * Collection Admin635		/// * Current NFT owner636		///637		/// # Arguments638		///639		/// * recipient: Address of token recipient.640		///641		/// * collection_id.642		///643		/// * item_id: ID of the item644		///     * Non-Fungible Mode: Required.645		///     * Fungible Mode: Ignored.646		///     * Re-Fungible Mode: Required.647		///648		/// * value: Amount to transfer.649		///     * Non-Fungible Mode: Ignored650		///     * Fungible Mode: Must specify transferred amount651		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)652		#[weight = <CommonWeights<T>>::transfer()]653		#[transactional]654		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {655			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);656657			dispatch_call::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value))658		}659660		/// Set, change, or remove approved address to transfer the ownership of the NFT.661		///662		/// # Permissions663		///664		/// * Collection Owner665		/// * Collection Admin666		/// * Current NFT owner667		///668		/// # Arguments669		///670		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).671		///672		/// * collection_id.673		///674		/// * item_id: ID of the item.675		#[weight = <CommonWeights<T>>::approve()]676		#[transactional]677		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {678			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);679680			dispatch_call::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))681		}682683		/// 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.684		///685		/// # Permissions686		/// * Collection Owner687		/// * Collection Admin688		/// * Current NFT owner689		/// * Address approved by current NFT owner690		///691		/// # Arguments692		///693		/// * from: Address that owns token.694		///695		/// * recipient: Address of token recipient.696		///697		/// * collection_id.698		///699		/// * item_id: ID of the item.700		///701		/// * value: Amount to transfer.702		#[weight = <CommonWeights<T>>::transfer_from()]703		#[transactional]704		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {705			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);706707			dispatch_call::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value))708		}709710		/// Set off-chain data schema.711		///712		/// # Permissions713		///714		/// * Collection Owner715		/// * Collection Admin716		///717		/// # Arguments718		///719		/// * collection_id.720		///721		/// * schema: String representing the offchain data schema.722		#[weight = <CommonWeights<T>>::set_variable_metadata(data.len() as u32)]723		#[transactional]724		pub fn set_variable_meta_data (725			origin,726			collection_id: CollectionId,727			item_id: TokenId,728			data: Vec<u8>729		) -> DispatchResultWithPostInfo {730			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);731732			dispatch_call::<T, _>(collection_id, |d| d.set_variable_metadata(sender, item_id, data))733		}734735		/// Set meta_update_permission value for particular collection736		///737		/// # Permissions738		///739		/// * Collection Owner.740		///741		/// # Arguments742		///743		/// * collection_id: ID of the collection.744		///745		/// * value: New flag value.746		#[weight = <SelfWeightOf<T>>::set_meta_update_permission_flag()]747		#[transactional]748		pub fn set_meta_update_permission_flag(origin, collection_id: CollectionId, value: MetaUpdatePermission) -> DispatchResult {749			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);750			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;751752			ensure!(753				target_collection.meta_update_permission != MetaUpdatePermission::None,754				<CommonError<T>>::MetadataFlagFrozen,755			);756			target_collection.check_is_owner(&sender)?;757758			target_collection.meta_update_permission = value;759760			target_collection.save()761		}762763		/// Set schema standard764		/// ImageURL765		/// Unique766		///767		/// # Permissions768		///769		/// * Collection Owner770		/// * Collection Admin771		///772		/// # Arguments773		///774		/// * collection_id.775		///776		/// * schema: SchemaVersion: enum777		#[weight = <SelfWeightOf<T>>::set_schema_version()]778		#[transactional]779		pub fn set_schema_version(780			origin,781			collection_id: CollectionId,782			version: SchemaVersion783		) -> DispatchResult {784			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);785			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;786			target_collection.check_is_owner_or_admin(&sender)?;787			target_collection.schema_version = version;788			target_collection.save()789		}790791		/// Set off-chain data schema.792		///793		/// # Permissions794		///795		/// * Collection Owner796		/// * Collection Admin797		///798		/// # Arguments799		///800		/// * collection_id.801		///802		/// * schema: String representing the offchain data schema.803		#[weight = <SelfWeightOf<T>>::set_offchain_schema(schema.len() as u32)]804		#[transactional]805		pub fn set_offchain_schema(806			origin,807			collection_id: CollectionId,808			schema: Vec<u8>809		) -> DispatchResult {810			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);811			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;812			target_collection.check_is_owner_or_admin(&sender)?;813814			// check schema limit815			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");816817			target_collection.offchain_schema = schema;818			target_collection.save()819		}820821		/// Set const on-chain data schema.822		///823		/// # Permissions824		///825		/// * Collection Owner826		/// * Collection Admin827		///828		/// # Arguments829		///830		/// * collection_id.831		///832		/// * schema: String representing the const on-chain data schema.833		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]834		#[transactional]835		pub fn set_const_on_chain_schema (836			origin,837			collection_id: CollectionId,838			schema: Vec<u8>839		) -> DispatchResult {840			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;842			target_collection.check_is_owner_or_admin(&sender)?;843844			// check schema limit845			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");846847			target_collection.const_on_chain_schema = schema;848			target_collection.save()849		}850851		/// Set variable on-chain data schema.852		///853		/// # Permissions854		///855		/// * Collection Owner856		/// * Collection Admin857		///858		/// # Arguments859		///860		/// * collection_id.861		///862		/// * schema: String representing the variable on-chain data schema.863		#[weight = <SelfWeightOf<T>>::set_const_on_chain_schema(schema.len() as u32)]864		#[transactional]865		pub fn set_variable_on_chain_schema (866			origin,867			collection_id: CollectionId,868			schema: Vec<u8>869		) -> DispatchResult {870			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;872			target_collection.check_is_owner_or_admin(&sender)?;873874			// check schema limit875			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");876877			target_collection.variable_on_chain_schema = schema;878			target_collection.save()879		}880881		#[weight = <SelfWeightOf<T>>::set_collection_limits()]882		#[transactional]883		pub fn set_collection_limits(884			origin,885			collection_id: CollectionId,886			new_limit: CollectionLimits,887		) -> DispatchResult {888			let mut new_limit = new_limit;889			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);890			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;891			target_collection.check_is_owner(&sender)?;892			let old_limit = &target_collection.limits;893894			macro_rules! limit_default {895				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{896					$(897						if let Some($new) = $new.$field {898							let $old = $old.$field($($arg)?);899							let _ = $new;900							let _ = $old;901							$check902						} else {903							$new.$field = $old.$field904						}905					)*906				}};907			}908909			limit_default!(old_limit, new_limit,910				account_token_ownership_limit => ensure!(911					new_limit <= MAX_TOKEN_OWNERSHIP,912					<Error<T>>::CollectionLimitBoundsExceeded,913				),914				sponsor_transfer_timeout(match target_collection.mode {915					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,916					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,917					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,918				}) => ensure!(919					new_limit <= MAX_SPONSOR_TIMEOUT,920					<Error<T>>::CollectionLimitBoundsExceeded,921				),922				sponsored_data_size => ensure!(923					new_limit <= CUSTOM_DATA_LIMIT,924					<Error<T>>::CollectionLimitBoundsExceeded,925				),926				token_limit => ensure!(927					old_limit >= new_limit && new_limit > 0,928					<CommonError<T>>::CollectionTokenLimitExceeded929				),930				owner_can_transfer => ensure!(931					old_limit || !new_limit,932					<Error<T>>::OwnerPermissionsCantBeReverted,933				),934				owner_can_destroy => ensure!(935					old_limit || !new_limit,936					<Error<T>>::OwnerPermissionsCantBeReverted,937				),938				sponsored_data_rate_limit => {},939				transfers_enabled => {},940			);941942			target_collection.limits = new_limit;943944			target_collection.save()945		}946	}947}948949// TODO: limit returned entries?950impl<T: Config> Pallet<T> {951	pub fn adminlist(collection: CollectionId) -> Vec<T::AccountId> {952		<IsAdmin<T>>::iter_prefix((collection,))953			.map(|(a, _)| a)954			.collect()955	}956	pub fn allowlist(collection: CollectionId) -> Vec<T::AccountId> {957		<Allowlist<T>>::iter_prefix((collection,))958			.map(|(a, _)| a)959			.collect()960	}961}
modifiedpallets/nft/src/sponsorship.rsdiffbeforeafterboth
--- a/pallets/nft/src/sponsorship.rs
+++ b/pallets/nft/src/sponsorship.rs
@@ -26,7 +26,13 @@
 		// sponsor timeout
 		let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 
-		let limit = collection.limits.sponsor_transfer_timeout;
+		let limit = collection
+			.limits
+			.sponsor_transfer_timeout(match _properties {
+				CreateItemData::NFT(_) => NFT_SPONSOR_TRANSFER_TIMEOUT,
+				CreateItemData::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+				CreateItemData::ReFungible(_) => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+			});
 		if CreateItemBasket::<T>::contains_key((collection_id, &who)) {
 			let last_tx_block = CreateItemBasket::<T>::get((collection_id, &who));
 			let limit_time = last_tx_block + limit.into();
@@ -37,7 +43,7 @@
 		CreateItemBasket::<T>::insert((collection_id, who.clone()), block_number);
 
 		// check free create limit
-		if collection.limits.sponsored_data_size >= (_properties.data_size() as u32) {
+		if collection.limits.sponsored_data_size() >= (_properties.data_size() as u32) {
 			collection.sponsorship.sponsor().cloned()
 		} else {
 			None
@@ -61,11 +67,8 @@
 			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 {
-						NFT_SPONSOR_TRANSFER_TIMEOUT
-					};
+					let limit =
+						collection_limits.sponsor_transfer_timeout(NFT_SPONSOR_TRANSFER_TIMEOUT);
 
 					let mut sponsored = true;
 					if NftTransferBasket::<T>::contains_key(collection_id, item_id) {
@@ -83,11 +86,8 @@
 				}
 				CollectionMode::Fungible(_) => {
 					// get correct limit
-					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
-						collection_limits.sponsor_transfer_timeout
-					} else {
-						FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
-					};
+					let limit = collection_limits
+						.sponsor_transfer_timeout(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
 
 					let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 					let mut sponsored = true;
@@ -106,11 +106,8 @@
 				}
 				CollectionMode::ReFungible => {
 					// get correct limit
-					let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
-						collection_limits.sponsor_transfer_timeout
-					} else {
-						REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT
-					};
+					let limit = collection_limits
+						.sponsor_transfer_timeout(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT);
 
 					let mut sponsored = true;
 					if ReFungibleTransferBasket::<T>::contains_key(collection_id, item_id) {
@@ -150,13 +147,13 @@
 			// Can't sponsor fungible collection, this tx will be rejected
 			// as invalid
 			!matches!(collection.mode, CollectionMode::Fungible(_)) &&
-			data.len() <= collection.limits.sponsored_data_size as usize
+			data.len() <= collection.limits.sponsored_data_size() as usize
 		{
-			if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit {
+			if let Some(rate_limit) = collection.limits.sponsored_data_rate_limit() {
 				let block_number = <frame_system::Pallet<T>>::block_number() as T::BlockNumber;
 
 				if VariableMetaDataBasket::<T>::get(collection_id, item_id)
-					.map(|last_block| block_number - last_block > rate_limit)
+					.map(|last_block| block_number - last_block > rate_limit.into())
 					.unwrap_or(true)
 				{
 					sponsor_metadata_changes = true;
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -164,7 +164,7 @@
 			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
 		ensure!(
 			&token_data.owner == sender
-				|| (collection.limits.owner_can_transfer
+				|| (collection.limits.owner_can_transfer()
 					&& collection.is_owner_or_admin(sender)?),
 			<CommonError<T>>::NoPermission
 		);
@@ -215,7 +215,7 @@
 		token: TokenId,
 	) -> DispatchResult {
 		ensure!(
-			collection.transfers_enabled,
+			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed
 		);
 
@@ -223,7 +223,8 @@
 			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
 		ensure!(
 			&token_data.owner == from
-				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),
+				|| (collection.limits.owner_can_transfer()
+					&& collection.is_owner_or_admin(from)?),
 			<CommonError<T>>::NoPermission
 		);
 
@@ -327,7 +328,7 @@
 			.checked_add(data.len() as u32)
 			.ok_or(ArithmeticError::Overflow)?;
 		ensure!(
-			tokens_minted < collection.limits.token_limit,
+			tokens_minted < collection.limits.token_limit(),
 			<CommonError<T>>::CollectionTokenLimitExceeded
 		);
 		collection.consume_sstore()?;
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -268,7 +268,7 @@
 		amount: u128,
 	) -> DispatchResult {
 		ensure!(
-			collection.transfers_enabled,
+			collection.limits.transfers_enabled(),
 			<CommonError<T>>::TransferNotAllowed
 		);
 
@@ -404,7 +404,7 @@
 			.checked_add(data.len() as u32)
 			.ok_or(ArithmeticError::Overflow)?;
 		ensure!(
-			tokens_minted < collection.limits.token_limit,
+			tokens_minted < collection.limits.token_limit(),
 			<CommonError<T>>::CollectionTokenLimitExceeded
 		);
 
modifiedprimitives/nft/src/lib.rsdiffbeforeafterboth
--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -42,6 +42,7 @@
 	10
 };
 pub const COLLECTION_ADMINS_LIMIT: u64 = 5;
+pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;
 pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {
 	1000000
 } else {
@@ -217,11 +218,10 @@
 	pub offchain_schema: Vec<u8>,
 	pub schema_version: SchemaVersion,
 	pub sponsorship: SponsorshipState<T::AccountId>,
-	pub limits: CollectionLimits<T::BlockNumber>, // Collection private restrictions
-	pub variable_on_chain_schema: Vec<u8>,        //
-	pub const_on_chain_schema: Vec<u8>,           //
+	pub limits: CollectionLimits,          // Collection private restrictions
+	pub variable_on_chain_schema: Vec<u8>, //
+	pub const_on_chain_schema: Vec<u8>,    //
 	pub meta_update_permission: MetaUpdatePermission,
-	pub transfers_enabled: bool,
 }
 
 #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
@@ -246,42 +246,57 @@
 	pub variable_data: Vec<u8>,
 }
 
-#[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
+#[derive(Encode, Decode, Debug, Default, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
-pub struct CollectionLimits<BlockNumber: Encode + Decode> {
+pub struct CollectionLimits {
 	pub account_token_ownership_limit: Option<u32>,
-	pub sponsored_data_size: u32,
+	pub sponsored_data_size: Option<u32>,
 	/// None - setVariableMetadata is not sponsored
 	/// Some(v) - setVariableMetadata is sponsored
 	///           if there is v block between txs
-	pub sponsored_data_rate_limit: Option<BlockNumber>,
-	pub token_limit: u32,
+	pub sponsored_data_rate_limit: Option<u32>,
+	pub token_limit: Option<u32>,
 
 	// Timeouts for item types in passed blocks
-	pub sponsor_transfer_timeout: u32,
-	pub owner_can_transfer: bool,
-	pub owner_can_destroy: bool,
+	pub sponsor_transfer_timeout: Option<u32>,
+	pub owner_can_transfer: Option<bool>,
+	pub owner_can_destroy: Option<bool>,
+	pub transfers_enabled: Option<bool>,
 }
 
-impl<BlockNumber: Encode + Decode> CollectionLimits<BlockNumber> {
+impl CollectionLimits {
 	pub fn account_token_ownership_limit(&self) -> u32 {
 		self.account_token_ownership_limit
 			.unwrap_or(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)
-			.min(ACCOUNT_TOKEN_OWNERSHIP_LIMIT)
+			.min(MAX_TOKEN_OWNERSHIP)
 	}
-}
-
-impl<BlockNumber: Encode + Decode> Default for CollectionLimits<BlockNumber> {
-	fn default() -> Self {
-		Self {
-			account_token_ownership_limit: Some(10_000_000),
-			token_limit: u32::max_value(),
-			sponsored_data_size: u32::MAX,
-			sponsored_data_rate_limit: None,
-			sponsor_transfer_timeout: 14400,
-			owner_can_transfer: true,
-			owner_can_destroy: true,
-		}
+	pub fn sponsored_data_size(&self) -> u32 {
+		self.sponsored_data_size
+			.unwrap_or(CUSTOM_DATA_LIMIT)
+			.min(CUSTOM_DATA_LIMIT)
+	}
+	pub fn token_limit(&self) -> u32 {
+		self.token_limit
+			.unwrap_or(COLLECTION_TOKEN_LIMIT)
+			.min(COLLECTION_TOKEN_LIMIT)
+	}
+	pub fn sponsor_transfer_timeout(&self, default: u32) -> u32 {
+		self.sponsor_transfer_timeout
+			.unwrap_or(default)
+			.min(MAX_SPONSOR_TIMEOUT)
+	}
+	pub fn owner_can_transfer(&self) -> bool {
+		self.owner_can_transfer.unwrap_or(true)
+	}
+	pub fn owner_can_destroy(&self) -> bool {
+		self.owner_can_destroy.unwrap_or(true)
+	}
+	pub fn transfers_enabled(&self) -> bool {
+		self.transfers_enabled.unwrap_or(true)
+	}
+	pub fn sponsored_data_rate_limit(&self) -> Option<u32> {
+		self.sponsored_data_rate_limit
+			.map(|v| v.min(MAX_SPONSOR_TIMEOUT))
 	}
 }