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
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -37,8 +37,9 @@
 use nft_data_structs::{
 	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
 	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,
-	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,
-	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
+	OFFCHAIN_SCHEMA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,
+	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
 };
 use pallet_common::{
 	account::CrossAccountId, CollectionHandle, IsAdmin, Pallet as PalletCommon,
@@ -188,11 +189,6 @@
 
 			// Anyone can create a collection
 			let who = ensure_signed(origin)?;
-
-			let limits = CollectionLimits::<T::BlockNumber> {
-				sponsored_data_size: CUSTOM_DATA_LIMIT,
-				..Default::default()
-			};
 
 			// Create new collection
 			let new_collection = Collection::<T> {
@@ -208,8 +204,7 @@
 				sponsorship: SponsorshipState::Disabled,
 				variable_on_chain_schema: Vec::new(),
 				const_on_chain_schema: Vec::new(),
-				limits,
-				transfers_enabled: true,
+				limits: Default::default(),
 				meta_update_permission: Default::default(),
 			};
 
@@ -582,7 +577,7 @@
 
 			// =========
 
-			target_collection.transfers_enabled = value;
+			target_collection.limits.transfers_enabled = Some(value);
 			target_collection.save()
 		}
 
@@ -888,30 +883,63 @@
 		pub fn set_collection_limits(
 			origin,
 			collection_id: CollectionId,
-			new_limits: CollectionLimits<T::BlockNumber>,
+			new_limit: CollectionLimits,
 		) -> DispatchResult {
+			let mut new_limit = new_limit;
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
 			target_collection.check_is_owner(&sender)?;
-			let old_limits = &target_collection.limits;
+			let old_limit = &target_collection.limits;
 
-			// collection bounds
-			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&
-				new_limits.account_token_ownership_limit.unwrap_or(0) <= MAX_TOKEN_OWNERSHIP &&
-				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,
-				Error::<T>::CollectionLimitBoundsExceeded);
+			macro_rules! limit_default {
+				($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{
+					$(
+						if let Some($new) = $new.$field {
+							let $old = $old.$field($($arg)?);
+							let _ = $new;
+							let _ = $old;
+							$check
+						} else {
+							$new.$field = $old.$field
+						}
+					)*
+				}};
+			}
 
-			// token_limit   check  prev
-			ensure!(old_limits.token_limit >= new_limits.token_limit, <CommonError<T>>::CollectionTokenLimitExceeded);
-			ensure!(new_limits.token_limit > 0, <CommonError<T>>::CollectionTokenLimitExceeded);
-
-			ensure!(
-				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&
-				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),
-				Error::<T>::OwnerPermissionsCantBeReverted,
+			limit_default!(old_limit, new_limit,
+				account_token_ownership_limit => ensure!(
+					new_limit <= MAX_TOKEN_OWNERSHIP,
+					<Error<T>>::CollectionLimitBoundsExceeded,
+				),
+				sponsor_transfer_timeout(match target_collection.mode {
+					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,
+					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+				}) => ensure!(
+					new_limit <= MAX_SPONSOR_TIMEOUT,
+					<Error<T>>::CollectionLimitBoundsExceeded,
+				),
+				sponsored_data_size => ensure!(
+					new_limit <= CUSTOM_DATA_LIMIT,
+					<Error<T>>::CollectionLimitBoundsExceeded,
+				),
+				token_limit => ensure!(
+					old_limit >= new_limit && new_limit > 0,
+					<CommonError<T>>::CollectionTokenLimitExceeded
+				),
+				owner_can_transfer => ensure!(
+					old_limit || !new_limit,
+					<Error<T>>::OwnerPermissionsCantBeReverted,
+				),
+				owner_can_destroy => ensure!(
+					old_limit || !new_limit,
+					<Error<T>>::OwnerPermissionsCantBeReverted,
+				),
+				sponsored_data_rate_limit => {},
+				transfers_enabled => {},
 			);
 
-			target_collection.limits = new_limits;
+			target_collection.limits = new_limit;
 
 			target_collection.save()
 		}
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
before · pallets/nonfungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use nft_data_structs::{6	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,7};8use pallet_common::{9	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use sp_core::H160;12use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};13use sp_std::{vec::Vec, vec};14use core::ops::Deref;15use sp_std::collections::btree_map::BTreeMap;16use codec::{Encode, Decode};17use scale_info::TypeInfo;1819pub use pallet::*;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod common;23pub mod erc;24pub mod weights;2526pub struct CreateItemData<T: Config> {27	pub const_data: BoundedVec<u8, CustomDataLimit>,28	pub variable_data: BoundedVec<u8, CustomDataLimit>,29	pub owner: T::CrossAccountId,30}31pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3233#[derive(Encode, Decode, TypeInfo)]34pub struct ItemData<T: Config> {35	pub const_data: Vec<u8>,36	pub variable_data: Vec<u8>,37	pub owner: T::CrossAccountId,38}3940#[frame_support::pallet]41pub mod pallet {42	use super::*;43	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};44	use nft_data_structs::{CollectionId, TokenId};45	use super::weights::WeightInfo;4647	#[pallet::error]48	pub enum Error<T> {49		/// Not Nonfungible item data used to mint in Nonfungible collection.50		NotNonfungibleDataUsedToMintFungibleCollectionToken,51		/// Used amount > 1 with NFT52		NonfungibleItemsHaveNoAmount,53	}5455	#[pallet::config]56	pub trait Config: frame_system::Config + pallet_common::Config {57		type WeightInfo: WeightInfo;58	}5960	#[pallet::pallet]61	#[pallet::generate_store(pub(super) trait Store)]62	pub struct Pallet<T>(_);6364	#[pallet::storage]65	pub(super) type TokensMinted<T: Config> =66		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;67	#[pallet::storage]68	pub(super) type TokensBurnt<T: Config> =69		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7071	#[pallet::storage]72	pub(super) type TokenData<T: Config> = StorageNMap<73		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),74		Value = ItemData<T>,75		QueryKind = OptionQuery,76	>;7778	/// Used to enumerate tokens owned by account79	#[pallet::storage]80	pub(super) type Owned<T: Config> = StorageNMap<81		Key = (82			Key<Twox64Concat, CollectionId>,83			Key<Blake2_128Concat, T::AccountId>,84			Key<Twox64Concat, TokenId>,85		),86		Value = bool,87		QueryKind = ValueQuery,88	>;8990	#[pallet::storage]91	pub(super) type AccountBalance<T: Config> = StorageNMap<92		Key = (93			Key<Twox64Concat, CollectionId>,94			Key<Blake2_128Concat, T::AccountId>,95		),96		Value = u32,97		QueryKind = ValueQuery,98	>;99100	#[pallet::storage]101	pub(super) type Allowance<T: Config> = StorageNMap<102		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),103		Value = T::CrossAccountId,104		QueryKind = OptionQuery,105	>;106}107108pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);109impl<T: Config> NonfungibleHandle<T> {110	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {111		Self(inner)112	}113	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {114		self.0115	}116}117impl<T: Config> Deref for NonfungibleHandle<T> {118	type Target = pallet_common::CollectionHandle<T>;119120	fn deref(&self) -> &Self::Target {121		&self.0122	}123}124125impl<T: Config> Pallet<T> {126	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {127		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)128	}129	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {130		<TokenData<T>>::contains_key((collection.id, token))131	}132}133134// unchecked calls skips any permission checks135impl<T: Config> Pallet<T> {136	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {137		PalletCommon::init_collection(data)138	}139	pub fn destroy_collection(140		collection: NonfungibleHandle<T>,141		sender: &T::CrossAccountId,142	) -> DispatchResult {143		let id = collection.id;144145		// =========146147		PalletCommon::destroy_collection(collection.0, sender)?;148149		<TokenData<T>>::remove_prefix((id,), None);150		<Owned<T>>::remove_prefix((id,), None);151		<TokensMinted<T>>::remove(id);152		<TokensBurnt<T>>::remove(id);153		<Allowance<T>>::remove_prefix((id,), None);154		<AccountBalance<T>>::remove_prefix((id,), None);155		Ok(())156	}157158	pub fn burn(159		collection: &NonfungibleHandle<T>,160		sender: &T::CrossAccountId,161		token: TokenId,162	) -> DispatchResult {163		let token_data = <TokenData<T>>::get((collection.id, token))164			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;165		ensure!(166			&token_data.owner == sender167				|| (collection.limits.owner_can_transfer168					&& collection.is_owner_or_admin(sender)?),169			<CommonError<T>>::NoPermission170		);171172		if collection.access == AccessMode::WhiteList {173			collection.check_allowlist(sender)?;174		}175176		let burnt = <TokensBurnt<T>>::get(collection.id)177			.checked_add(1)178			.ok_or(ArithmeticError::Overflow)?;179180		// =========181182		<Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));183		<TokensBurnt<T>>::insert(collection.id, burnt);184		<TokenData<T>>::remove((collection.id, token));185		let old_spender = <Allowance<T>>::take((collection.id, token));186187		if let Some(old_spender) = old_spender {188			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(189				collection.id,190				token,191				sender.clone(),192				old_spender.clone(),193				0,194			));195		}196197		collection.log_infallible(ERC721Events::Transfer {198			from: *token_data.owner.as_eth(),199			to: H160::default(),200			token_id: token.into(),201		});202		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(203			collection.id,204			token,205			token_data.owner,206			1,207		));208		return Ok(());209	}210211	pub fn transfer(212		collection: &NonfungibleHandle<T>,213		from: &T::CrossAccountId,214		to: &T::CrossAccountId,215		token: TokenId,216	) -> DispatchResult {217		ensure!(218			collection.transfers_enabled,219			<CommonError<T>>::TransferNotAllowed220		);221222		let token_data = <TokenData<T>>::get((collection.id, token))223			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;224		ensure!(225			&token_data.owner == from226				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),227			<CommonError<T>>::NoPermission228		);229230		if collection.access == AccessMode::WhiteList {231			collection.check_allowlist(from)?;232			collection.check_allowlist(to)?;233		}234		<PalletCommon<T>>::ensure_correct_receiver(to)?;235236		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))237			.checked_sub(1)238			.ok_or(<CommonError<T>>::TokenValueTooLow)?;239		let balance_to = if from != to {240			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))241				.checked_add(1)242				.ok_or(ArithmeticError::Overflow)?;243244			ensure!(245				balance_to < collection.limits.account_token_ownership_limit(),246				<CommonError<T>>::AccountTokenLimitExceeded,247			);248249			Some(balance_to)250		} else {251			None252		};253254		collection.consume_sstores(4)?;255		collection.consume_log(3, 0)?;256257		// =========258259		<TokenData<T>>::insert(260			(collection.id, token),261			ItemData {262				owner: to.clone(),263				..token_data264			},265		);266267		if let Some(balance_to) = balance_to {268			// from != to269			if balance_from == 0 {270				<AccountBalance<T>>::remove((collection.id, from.as_sub()));271			} else {272				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);273			}274			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);275			<Owned<T>>::remove((collection.id, from.as_sub(), token));276			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);277		}278		Self::set_allowance_unchecked(collection, from, token, None, true);279280		collection.log_infallible(ERC721Events::Transfer {281			from: *from.as_eth(),282			to: *to.as_eth(),283			token_id: token.into(),284		});285		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(286			collection.id,287			token,288			from.clone(),289			to.clone(),290			1,291		));292		Ok(())293	}294295	pub fn create_multiple_items(296		collection: &NonfungibleHandle<T>,297		sender: &T::CrossAccountId,298		data: Vec<CreateItemData<T>>,299	) -> DispatchResult {300		let unrestricted_minting = collection.is_owner_or_admin(sender)?;301		if !unrestricted_minting {302			ensure!(303				collection.mint_mode,304				<CommonError<T>>::PublicMintingNotAllowed305			);306			collection.check_allowlist(sender)?;307308			for item in data.iter() {309				collection.check_allowlist(&item.owner)?;310			}311		}312313		for data in data.iter() {314			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;315			if !data.const_data.is_empty() {316				collection.consume_sstore()?;317			}318			if !data.variable_data.is_empty() {319				collection.consume_sstore()?;320			}321			collection.consume_sstore()?;322			collection.consume_log(3, 0)?;323		}324325		let first_token = <TokensMinted<T>>::get(collection.id);326		let tokens_minted = first_token327			.checked_add(data.len() as u32)328			.ok_or(ArithmeticError::Overflow)?;329		ensure!(330			tokens_minted < collection.limits.token_limit,331			<CommonError<T>>::CollectionTokenLimitExceeded332		);333		collection.consume_sstore()?;334335		let mut balances = BTreeMap::new();336		for data in &data {337			let balance = balances338				.entry(data.owner.as_sub())339				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));340			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;341342			ensure!(343				*balance <= collection.limits.account_token_ownership_limit(),344				<CommonError<T>>::AccountTokenLimitExceeded,345			);346		}347		collection.consume_sstores(balances.len())?;348349		// =========350351		<TokensMinted<T>>::insert(collection.id, tokens_minted);352		for (account, balance) in balances {353			<AccountBalance<T>>::insert((collection.id, account), balance);354		}355		for (i, data) in data.into_iter().enumerate() {356			let token = first_token + i as u32 + 1;357358			<TokenData<T>>::insert(359				(collection.id, token),360				ItemData {361					const_data: data.const_data.into(),362					variable_data: data.variable_data.into(),363					owner: data.owner.clone(),364				},365			);366			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);367368			collection.log_infallible(ERC721Events::Transfer {369				from: H160::default(),370				to: *data.owner.as_eth(),371				token_id: token.into(),372			});373			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(374				collection.id,375				TokenId(token),376				data.owner.clone(),377				1,378			));379		}380		Ok(())381	}382383	pub fn set_allowance_unchecked(384		collection: &NonfungibleHandle<T>,385		sender: &T::CrossAccountId,386		token: TokenId,387		spender: Option<&T::CrossAccountId>,388		assume_implicit_eth: bool,389	) {390		if let Some(spender) = spender {391			let old_spender = <Allowance<T>>::get((collection.id, token));392			<Allowance<T>>::insert((collection.id, token), spender);393			// In ERC721 there is only one possible approved user of token, so we set394			// approved user to spender395			collection.log_infallible(ERC721Events::Approval {396				owner: *sender.as_eth(),397				approved: *spender.as_eth(),398				token_id: token.into(),399			});400			// In Unique chain, any token can have any amount of approved users, so we need to401			// set allowance of old owner to 0, and allowance of new owner to 1402			if old_spender.as_ref() != Some(spender) {403				if let Some(old_owner) = old_spender {404					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(405						collection.id,406						token,407						sender.clone(),408						old_owner.clone(),409						0,410					));411				}412				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(413					collection.id,414					token,415					sender.clone(),416					spender.clone(),417					1,418				));419			}420		} else {421			let old_spender = <Allowance<T>>::take((collection.id, token));422			if !assume_implicit_eth {423				// In ERC721 there is only one possible approved user of token, so we set424				// approved user to zero address425				collection.log_infallible(ERC721Events::Approval {426					owner: *sender.as_eth(),427					approved: H160::default(),428					token_id: token.into(),429				});430			}431			// In Unique chain, any token can have any amount of approved users, so we need to432			// set allowance of old owner to 0433			if let Some(old_spender) = old_spender {434				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(435					collection.id,436					token,437					sender.clone(),438					old_spender.clone(),439					0,440				));441			}442		}443	}444445	pub fn set_allowance(446		collection: &NonfungibleHandle<T>,447		sender: &T::CrossAccountId,448		token: TokenId,449		spender: Option<&T::CrossAccountId>,450	) -> DispatchResult {451		if collection.access == AccessMode::WhiteList {452			collection.check_allowlist(&sender)?;453			if let Some(spender) = spender {454				collection.check_allowlist(&spender)?;455			}456		}457458		if let Some(spender) = spender {459			<PalletCommon<T>>::ensure_correct_receiver(spender)?;460		}461		let token_data =462			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;463		if &token_data.owner != sender {464			ensure!(465				collection.ignores_owned_amount(sender)?,466				<CommonError<T>>::CantApproveMoreThanOwned467			);468		}469470		// =========471472		Self::set_allowance_unchecked(collection, sender, token, spender, false);473		Ok(())474	}475476	pub fn transfer_from(477		collection: &NonfungibleHandle<T>,478		spender: &T::CrossAccountId,479		from: &T::CrossAccountId,480		to: &T::CrossAccountId,481		token: TokenId,482	) -> DispatchResult {483		if spender == from {484			return Self::transfer(collection, from, to, token);485		}486		if collection.access == AccessMode::WhiteList {487			// `from`, `to` checked in [`transfer`]488			collection.check_allowlist(spender)?;489		}490491		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {492			ensure!(493				collection.ignores_allowance(spender)?,494				<CommonError<T>>::TokenValueNotEnough495			);496		}497498		// =========499500		Self::transfer(collection, &from, to, token)?;501		// Allowance is reset in [`transfer`]502		Ok(())503	}504505	pub fn burn_from(506		collection: &NonfungibleHandle<T>,507		spender: &T::CrossAccountId,508		from: &T::CrossAccountId,509		token: TokenId,510	) -> DispatchResult {511		if spender == from {512			return Self::burn(collection, from, token);513		}514		if collection.access == AccessMode::WhiteList {515			// `from` checked in [`burn`]516			collection.check_allowlist(spender)?;517		}518519		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {520			ensure!(521				collection.ignores_allowance(spender)?,522				<CommonError<T>>::TokenValueNotEnough523			);524		}525526		// =========527528		Self::burn(collection, &from, token)529	}530531	pub fn set_variable_metadata(532		collection: &NonfungibleHandle<T>,533		sender: &T::CrossAccountId,534		token: TokenId,535		data: Vec<u8>,536	) -> DispatchResult {537		ensure!(538			data.len() as u32 <= CUSTOM_DATA_LIMIT,539			<CommonError<T>>::TokenVariableDataLimitExceeded540		);541		let token_data =542			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;543		collection.check_can_update_meta(sender, &token_data.owner)?;544545		collection.consume_sstore()?;546547		// =========548549		<TokenData<T>>::insert(550			(collection.id, token),551			ItemData {552				variable_data: data,553				..token_data554			},555		);556		Ok(())557	}558559	/// Delegated to `create_multiple_items`560	pub fn create_item(561		collection: &NonfungibleHandle<T>,562		sender: &T::CrossAccountId,563		data: CreateItemData<T>,564	) -> DispatchResult {565		Self::create_multiple_items(collection, sender, vec![data])566	}567}
after · pallets/nonfungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use nft_data_structs::{6	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,7};8use pallet_common::{9	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use sp_core::H160;12use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};13use sp_std::{vec::Vec, vec};14use core::ops::Deref;15use sp_std::collections::btree_map::BTreeMap;16use codec::{Encode, Decode};17use scale_info::TypeInfo;1819pub use pallet::*;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod common;23pub mod erc;24pub mod weights;2526pub struct CreateItemData<T: Config> {27	pub const_data: BoundedVec<u8, CustomDataLimit>,28	pub variable_data: BoundedVec<u8, CustomDataLimit>,29	pub owner: T::CrossAccountId,30}31pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3233#[derive(Encode, Decode, TypeInfo)]34pub struct ItemData<T: Config> {35	pub const_data: Vec<u8>,36	pub variable_data: Vec<u8>,37	pub owner: T::CrossAccountId,38}3940#[frame_support::pallet]41pub mod pallet {42	use super::*;43	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};44	use nft_data_structs::{CollectionId, TokenId};45	use super::weights::WeightInfo;4647	#[pallet::error]48	pub enum Error<T> {49		/// Not Nonfungible item data used to mint in Nonfungible collection.50		NotNonfungibleDataUsedToMintFungibleCollectionToken,51		/// Used amount > 1 with NFT52		NonfungibleItemsHaveNoAmount,53	}5455	#[pallet::config]56	pub trait Config: frame_system::Config + pallet_common::Config {57		type WeightInfo: WeightInfo;58	}5960	#[pallet::pallet]61	#[pallet::generate_store(pub(super) trait Store)]62	pub struct Pallet<T>(_);6364	#[pallet::storage]65	pub(super) type TokensMinted<T: Config> =66		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;67	#[pallet::storage]68	pub(super) type TokensBurnt<T: Config> =69		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;7071	#[pallet::storage]72	pub(super) type TokenData<T: Config> = StorageNMap<73		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),74		Value = ItemData<T>,75		QueryKind = OptionQuery,76	>;7778	/// Used to enumerate tokens owned by account79	#[pallet::storage]80	pub(super) type Owned<T: Config> = StorageNMap<81		Key = (82			Key<Twox64Concat, CollectionId>,83			Key<Blake2_128Concat, T::AccountId>,84			Key<Twox64Concat, TokenId>,85		),86		Value = bool,87		QueryKind = ValueQuery,88	>;8990	#[pallet::storage]91	pub(super) type AccountBalance<T: Config> = StorageNMap<92		Key = (93			Key<Twox64Concat, CollectionId>,94			Key<Blake2_128Concat, T::AccountId>,95		),96		Value = u32,97		QueryKind = ValueQuery,98	>;99100	#[pallet::storage]101	pub(super) type Allowance<T: Config> = StorageNMap<102		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),103		Value = T::CrossAccountId,104		QueryKind = OptionQuery,105	>;106}107108pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);109impl<T: Config> NonfungibleHandle<T> {110	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {111		Self(inner)112	}113	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {114		self.0115	}116}117impl<T: Config> Deref for NonfungibleHandle<T> {118	type Target = pallet_common::CollectionHandle<T>;119120	fn deref(&self) -> &Self::Target {121		&self.0122	}123}124125impl<T: Config> Pallet<T> {126	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {127		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)128	}129	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {130		<TokenData<T>>::contains_key((collection.id, token))131	}132}133134// unchecked calls skips any permission checks135impl<T: Config> Pallet<T> {136	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {137		PalletCommon::init_collection(data)138	}139	pub fn destroy_collection(140		collection: NonfungibleHandle<T>,141		sender: &T::CrossAccountId,142	) -> DispatchResult {143		let id = collection.id;144145		// =========146147		PalletCommon::destroy_collection(collection.0, sender)?;148149		<TokenData<T>>::remove_prefix((id,), None);150		<Owned<T>>::remove_prefix((id,), None);151		<TokensMinted<T>>::remove(id);152		<TokensBurnt<T>>::remove(id);153		<Allowance<T>>::remove_prefix((id,), None);154		<AccountBalance<T>>::remove_prefix((id,), None);155		Ok(())156	}157158	pub fn burn(159		collection: &NonfungibleHandle<T>,160		sender: &T::CrossAccountId,161		token: TokenId,162	) -> DispatchResult {163		let token_data = <TokenData<T>>::get((collection.id, token))164			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;165		ensure!(166			&token_data.owner == sender167				|| (collection.limits.owner_can_transfer()168					&& collection.is_owner_or_admin(sender)?),169			<CommonError<T>>::NoPermission170		);171172		if collection.access == AccessMode::WhiteList {173			collection.check_allowlist(sender)?;174		}175176		let burnt = <TokensBurnt<T>>::get(collection.id)177			.checked_add(1)178			.ok_or(ArithmeticError::Overflow)?;179180		// =========181182		<Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));183		<TokensBurnt<T>>::insert(collection.id, burnt);184		<TokenData<T>>::remove((collection.id, token));185		let old_spender = <Allowance<T>>::take((collection.id, token));186187		if let Some(old_spender) = old_spender {188			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(189				collection.id,190				token,191				sender.clone(),192				old_spender.clone(),193				0,194			));195		}196197		collection.log_infallible(ERC721Events::Transfer {198			from: *token_data.owner.as_eth(),199			to: H160::default(),200			token_id: token.into(),201		});202		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(203			collection.id,204			token,205			token_data.owner,206			1,207		));208		return Ok(());209	}210211	pub fn transfer(212		collection: &NonfungibleHandle<T>,213		from: &T::CrossAccountId,214		to: &T::CrossAccountId,215		token: TokenId,216	) -> DispatchResult {217		ensure!(218			collection.limits.transfers_enabled(),219			<CommonError<T>>::TransferNotAllowed220		);221222		let token_data = <TokenData<T>>::get((collection.id, token))223			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;224		ensure!(225			&token_data.owner == from226				|| (collection.limits.owner_can_transfer()227					&& collection.is_owner_or_admin(from)?),228			<CommonError<T>>::NoPermission229		);230231		if collection.access == AccessMode::WhiteList {232			collection.check_allowlist(from)?;233			collection.check_allowlist(to)?;234		}235		<PalletCommon<T>>::ensure_correct_receiver(to)?;236237		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))238			.checked_sub(1)239			.ok_or(<CommonError<T>>::TokenValueTooLow)?;240		let balance_to = if from != to {241			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))242				.checked_add(1)243				.ok_or(ArithmeticError::Overflow)?;244245			ensure!(246				balance_to < collection.limits.account_token_ownership_limit(),247				<CommonError<T>>::AccountTokenLimitExceeded,248			);249250			Some(balance_to)251		} else {252			None253		};254255		collection.consume_sstores(4)?;256		collection.consume_log(3, 0)?;257258		// =========259260		<TokenData<T>>::insert(261			(collection.id, token),262			ItemData {263				owner: to.clone(),264				..token_data265			},266		);267268		if let Some(balance_to) = balance_to {269			// from != to270			if balance_from == 0 {271				<AccountBalance<T>>::remove((collection.id, from.as_sub()));272			} else {273				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);274			}275			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);276			<Owned<T>>::remove((collection.id, from.as_sub(), token));277			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);278		}279		Self::set_allowance_unchecked(collection, from, token, None, true);280281		collection.log_infallible(ERC721Events::Transfer {282			from: *from.as_eth(),283			to: *to.as_eth(),284			token_id: token.into(),285		});286		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(287			collection.id,288			token,289			from.clone(),290			to.clone(),291			1,292		));293		Ok(())294	}295296	pub fn create_multiple_items(297		collection: &NonfungibleHandle<T>,298		sender: &T::CrossAccountId,299		data: Vec<CreateItemData<T>>,300	) -> DispatchResult {301		let unrestricted_minting = collection.is_owner_or_admin(sender)?;302		if !unrestricted_minting {303			ensure!(304				collection.mint_mode,305				<CommonError<T>>::PublicMintingNotAllowed306			);307			collection.check_allowlist(sender)?;308309			for item in data.iter() {310				collection.check_allowlist(&item.owner)?;311			}312		}313314		for data in data.iter() {315			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;316			if !data.const_data.is_empty() {317				collection.consume_sstore()?;318			}319			if !data.variable_data.is_empty() {320				collection.consume_sstore()?;321			}322			collection.consume_sstore()?;323			collection.consume_log(3, 0)?;324		}325326		let first_token = <TokensMinted<T>>::get(collection.id);327		let tokens_minted = first_token328			.checked_add(data.len() as u32)329			.ok_or(ArithmeticError::Overflow)?;330		ensure!(331			tokens_minted < collection.limits.token_limit(),332			<CommonError<T>>::CollectionTokenLimitExceeded333		);334		collection.consume_sstore()?;335336		let mut balances = BTreeMap::new();337		for data in &data {338			let balance = balances339				.entry(data.owner.as_sub())340				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));341			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;342343			ensure!(344				*balance <= collection.limits.account_token_ownership_limit(),345				<CommonError<T>>::AccountTokenLimitExceeded,346			);347		}348		collection.consume_sstores(balances.len())?;349350		// =========351352		<TokensMinted<T>>::insert(collection.id, tokens_minted);353		for (account, balance) in balances {354			<AccountBalance<T>>::insert((collection.id, account), balance);355		}356		for (i, data) in data.into_iter().enumerate() {357			let token = first_token + i as u32 + 1;358359			<TokenData<T>>::insert(360				(collection.id, token),361				ItemData {362					const_data: data.const_data.into(),363					variable_data: data.variable_data.into(),364					owner: data.owner.clone(),365				},366			);367			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);368369			collection.log_infallible(ERC721Events::Transfer {370				from: H160::default(),371				to: *data.owner.as_eth(),372				token_id: token.into(),373			});374			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(375				collection.id,376				TokenId(token),377				data.owner.clone(),378				1,379			));380		}381		Ok(())382	}383384	pub fn set_allowance_unchecked(385		collection: &NonfungibleHandle<T>,386		sender: &T::CrossAccountId,387		token: TokenId,388		spender: Option<&T::CrossAccountId>,389		assume_implicit_eth: bool,390	) {391		if let Some(spender) = spender {392			let old_spender = <Allowance<T>>::get((collection.id, token));393			<Allowance<T>>::insert((collection.id, token), spender);394			// In ERC721 there is only one possible approved user of token, so we set395			// approved user to spender396			collection.log_infallible(ERC721Events::Approval {397				owner: *sender.as_eth(),398				approved: *spender.as_eth(),399				token_id: token.into(),400			});401			// In Unique chain, any token can have any amount of approved users, so we need to402			// set allowance of old owner to 0, and allowance of new owner to 1403			if old_spender.as_ref() != Some(spender) {404				if let Some(old_owner) = old_spender {405					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(406						collection.id,407						token,408						sender.clone(),409						old_owner.clone(),410						0,411					));412				}413				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(414					collection.id,415					token,416					sender.clone(),417					spender.clone(),418					1,419				));420			}421		} else {422			let old_spender = <Allowance<T>>::take((collection.id, token));423			if !assume_implicit_eth {424				// In ERC721 there is only one possible approved user of token, so we set425				// approved user to zero address426				collection.log_infallible(ERC721Events::Approval {427					owner: *sender.as_eth(),428					approved: H160::default(),429					token_id: token.into(),430				});431			}432			// In Unique chain, any token can have any amount of approved users, so we need to433			// set allowance of old owner to 0434			if let Some(old_spender) = old_spender {435				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(436					collection.id,437					token,438					sender.clone(),439					old_spender.clone(),440					0,441				));442			}443		}444	}445446	pub fn set_allowance(447		collection: &NonfungibleHandle<T>,448		sender: &T::CrossAccountId,449		token: TokenId,450		spender: Option<&T::CrossAccountId>,451	) -> DispatchResult {452		if collection.access == AccessMode::WhiteList {453			collection.check_allowlist(&sender)?;454			if let Some(spender) = spender {455				collection.check_allowlist(&spender)?;456			}457		}458459		if let Some(spender) = spender {460			<PalletCommon<T>>::ensure_correct_receiver(spender)?;461		}462		let token_data =463			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;464		if &token_data.owner != sender {465			ensure!(466				collection.ignores_owned_amount(sender)?,467				<CommonError<T>>::CantApproveMoreThanOwned468			);469		}470471		// =========472473		Self::set_allowance_unchecked(collection, sender, token, spender, false);474		Ok(())475	}476477	pub fn transfer_from(478		collection: &NonfungibleHandle<T>,479		spender: &T::CrossAccountId,480		from: &T::CrossAccountId,481		to: &T::CrossAccountId,482		token: TokenId,483	) -> DispatchResult {484		if spender == from {485			return Self::transfer(collection, from, to, token);486		}487		if collection.access == AccessMode::WhiteList {488			// `from`, `to` checked in [`transfer`]489			collection.check_allowlist(spender)?;490		}491492		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {493			ensure!(494				collection.ignores_allowance(spender)?,495				<CommonError<T>>::TokenValueNotEnough496			);497		}498499		// =========500501		Self::transfer(collection, &from, to, token)?;502		// Allowance is reset in [`transfer`]503		Ok(())504	}505506	pub fn burn_from(507		collection: &NonfungibleHandle<T>,508		spender: &T::CrossAccountId,509		from: &T::CrossAccountId,510		token: TokenId,511	) -> DispatchResult {512		if spender == from {513			return Self::burn(collection, from, token);514		}515		if collection.access == AccessMode::WhiteList {516			// `from` checked in [`burn`]517			collection.check_allowlist(spender)?;518		}519520		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {521			ensure!(522				collection.ignores_allowance(spender)?,523				<CommonError<T>>::TokenValueNotEnough524			);525		}526527		// =========528529		Self::burn(collection, &from, token)530	}531532	pub fn set_variable_metadata(533		collection: &NonfungibleHandle<T>,534		sender: &T::CrossAccountId,535		token: TokenId,536		data: Vec<u8>,537	) -> DispatchResult {538		ensure!(539			data.len() as u32 <= CUSTOM_DATA_LIMIT,540			<CommonError<T>>::TokenVariableDataLimitExceeded541		);542		let token_data =543			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;544		collection.check_can_update_meta(sender, &token_data.owner)?;545546		collection.consume_sstore()?;547548		// =========549550		<TokenData<T>>::insert(551			(collection.id, token),552			ItemData {553				variable_data: data,554				..token_data555			},556		);557		Ok(())558	}559560	/// Delegated to `create_multiple_items`561	pub fn create_item(562		collection: &NonfungibleHandle<T>,563		sender: &T::CrossAccountId,564		data: CreateItemData<T>,565	) -> DispatchResult {566		Self::create_multiple_items(collection, sender, vec![data])567	}568}
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))
 	}
 }