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

difftreelog

feat allow more fields to be set on collection creation

Yaroslav Bolyukin2022-01-11parent: #fcf0631.patch.diff
in: master

7 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -14,7 +14,10 @@
 	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,
 	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
 	COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,
-	WithdrawReasons, CollectionStats,
+	WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode,
+	NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,
+	CreateCollectionData, SponsorshipState,
 };
 pub use pallet::*;
 use sp_core::H160;
@@ -282,6 +285,10 @@
 		TokenVariableDataLimitExceeded,
 		/// Exceeded max admin count
 		CollectionAdminCountExceeded,
+		/// Collection limit bounds per collection exceeded
+		CollectionLimitBoundsExceeded,
+		/// Tried to enable permissions which are only permitted to be disabled
+		OwnerPermissionsCantBeReverted,
 
 		/// Collection settings not allowing items transferring
 		TransferNotAllowed,
@@ -392,7 +399,10 @@
 }
 
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
 		{
 			ensure!(
 				data.name.len() <= MAX_COLLECTION_NAME_LENGTH,
@@ -423,6 +433,29 @@
 
 		// =========
 
+		let collection = Collection {
+			owner: owner.clone(),
+			name: data.name,
+			mode: data.mode.clone(),
+			mint_mode: false,
+			access: data.access.unwrap_or_default(),
+			description: data.description,
+			token_prefix: data.token_prefix,
+			offchain_schema: data.offchain_schema,
+			schema_version: data.schema_version.unwrap_or_default(),
+			sponsorship: data
+				.pending_sponsor
+				.map(SponsorshipState::Unconfirmed)
+				.unwrap_or_default(),
+			variable_on_chain_schema: data.variable_on_chain_schema,
+			const_on_chain_schema: data.const_on_chain_schema,
+			limits: data
+				.limits
+				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))
+				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,
+			meta_update_permission: data.meta_update_permission.unwrap_or_default(),
+		};
+
 		// Take a (non-refundable) deposit of collection creation
 		{
 			let mut imbalance =
@@ -434,7 +467,7 @@
 				),
 			);
 			<T as Config>::Currency::settle(
-				&data.owner,
+				&owner,
 				imbalance,
 				WithdrawReasons::TRANSFER,
 				ExistenceRequirement::KeepAlive,
@@ -443,12 +476,8 @@
 		}
 
 		<CreatedCollectionCount<T>>::put(created_count);
-		<Pallet<T>>::deposit_event(Event::CollectionCreated(
-			id,
-			data.mode.id(),
-			data.owner.clone(),
-		));
-		<CollectionById<T>>::insert(id, data);
+		<Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));
+		<CollectionById<T>>::insert(id, collection);
 		Ok(id)
 	}
 
@@ -532,6 +561,61 @@
 
 		Ok(())
 	}
+
+	pub fn clamp_limits(
+		mode: CollectionMode,
+		old_limit: &CollectionLimits,
+		mut new_limit: CollectionLimits,
+	) -> Result<CollectionLimits, DispatchError> {
+		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
+						}
+					)*
+				}};
+			}
+
+		limit_default!(old_limit, new_limit,
+			account_token_ownership_limit => ensure!(
+				new_limit <= MAX_TOKEN_OWNERSHIP,
+				<Error<T>>::CollectionLimitBoundsExceeded,
+			),
+			sponsor_transfer_timeout(match 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,
+				<Error<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 => {},
+		);
+		Ok(new_limit)
+	}
 }
 
 #[macro_export]
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
before · pallets/fungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::Deref;4use frame_support::{ensure};5use up_data_structs::{AccessMode, Collection, CollectionId, TokenId};6use pallet_common::{7	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,8};9use pallet_evm_coder_substrate::WithRecorder;10use sp_core::H160;11use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};12use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};1314pub use pallet::*;1516use crate::erc::ERC20Events;17#[cfg(feature = "runtime-benchmarks")]18pub mod benchmarking;19pub mod common;20pub mod erc;21pub mod weights;2223pub type CreateItemData<T> = (<T as pallet_common::Config>::CrossAccountId, u128);24pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;2526#[frame_support::pallet]27pub mod pallet {28	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};29	use up_data_structs::CollectionId;30	use super::weights::WeightInfo;3132	#[pallet::error]33	pub enum Error<T> {34		/// Not Fungible item data used to mint in Fungible collection.35		NotFungibleDataUsedToMintFungibleCollectionToken,36		/// Not default id passed as TokenId argument37		FungibleItemsHaveNoId,38		/// Tried to set data for fungible item39		FungibleItemsDontHaveData,40	}4142	#[pallet::config]43	pub trait Config: frame_system::Config + pallet_common::Config {44		type WeightInfo: WeightInfo;45	}4647	#[pallet::pallet]48	#[pallet::generate_store(pub(super) trait Store)]49	pub struct Pallet<T>(_);5051	#[pallet::storage]52	pub type TotalSupply<T: Config> =53		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;5455	#[pallet::storage]56	pub type Balance<T: Config> = StorageNMap<57		Key = (58			Key<Twox64Concat, CollectionId>,59			Key<Blake2_128Concat, T::CrossAccountId>,60		),61		Value = u128,62		QueryKind = ValueQuery,63	>;6465	#[pallet::storage]66	pub type Allowance<T: Config> = StorageNMap<67		Key = (68			Key<Twox64Concat, CollectionId>,69			Key<Blake2_128, T::CrossAccountId>,70			Key<Blake2_128Concat, T::CrossAccountId>,71		),72		Value = u128,73		QueryKind = ValueQuery,74	>;75}7677pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);78impl<T: Config> FungibleHandle<T> {79	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {80		Self(inner)81	}82	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {83		self.084	}85}86impl<T: Config> WithRecorder<T> for FungibleHandle<T> {87	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {88		self.0.recorder()89	}90	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {91		self.0.into_recorder()92	}93}94impl<T: Config> Deref for FungibleHandle<T> {95	type Target = pallet_common::CollectionHandle<T>;9697	fn deref(&self) -> &Self::Target {98		&self.099	}100}101102impl<T: Config> Pallet<T> {103	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {104		<PalletCommon<T>>::init_collection(data)105	}106	pub fn destroy_collection(107		collection: FungibleHandle<T>,108		sender: &T::CrossAccountId,109	) -> DispatchResult {110		let id = collection.id;111112		// =========113114		PalletCommon::destroy_collection(collection.0, sender)?;115116		<TotalSupply<T>>::remove(id);117		<Balance<T>>::remove_prefix((id,), None);118		<Allowance<T>>::remove_prefix((id,), None);119		Ok(())120	}121122	pub fn burn(123		collection: &FungibleHandle<T>,124		owner: &T::CrossAccountId,125		amount: u128,126	) -> DispatchResult {127		let total_supply = <TotalSupply<T>>::get(collection.id)128			.checked_sub(amount)129			.ok_or(<CommonError<T>>::TokenValueTooLow)?;130131		let balance = <Balance<T>>::get((collection.id, owner))132			.checked_sub(amount)133			.ok_or(<CommonError<T>>::TokenValueTooLow)?;134135		if collection.access == AccessMode::AllowList {136			collection.check_allowlist(owner)?;137		}138139		// =========140141		if balance == 0 {142			<Balance<T>>::remove((collection.id, owner));143		} else {144			<Balance<T>>::insert((collection.id, owner), balance);145		}146		<TotalSupply<T>>::insert(collection.id, total_supply);147148		collection.log(ERC20Events::Transfer {149			from: *owner.as_eth(),150			to: H160::default(),151			value: amount.into(),152		});153		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(154			collection.id,155			TokenId::default(),156			owner.clone(),157			amount,158		));159		Ok(())160	}161162	pub fn transfer(163		collection: &FungibleHandle<T>,164		from: &T::CrossAccountId,165		to: &T::CrossAccountId,166		amount: u128,167	) -> DispatchResult {168		ensure!(169			collection.limits.transfers_enabled(),170			<CommonError<T>>::TransferNotAllowed,171		);172173		if collection.access == AccessMode::AllowList {174			collection.check_allowlist(from)?;175			collection.check_allowlist(to)?;176		}177		<PalletCommon<T>>::ensure_correct_receiver(to)?;178179		let balance_from = <Balance<T>>::get((collection.id, from))180			.checked_sub(amount)181			.ok_or(<CommonError<T>>::TokenValueTooLow)?;182		let balance_to = if from != to {183			Some(184				<Balance<T>>::get((collection.id, to))185					.checked_add(amount)186					.ok_or(ArithmeticError::Overflow)?,187			)188		} else {189			None190		};191192		// =========193194		if let Some(balance_to) = balance_to {195			// from != to196			if balance_from == 0 {197				<Balance<T>>::remove((collection.id, from));198			} else {199				<Balance<T>>::insert((collection.id, from), balance_from);200			}201			<Balance<T>>::insert((collection.id, to), balance_to);202		}203204		collection.log(ERC20Events::Transfer {205			from: *from.as_eth(),206			to: *to.as_eth(),207			value: amount.into(),208		});209		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(210			collection.id,211			TokenId::default(),212			from.clone(),213			to.clone(),214			amount,215		));216		Ok(())217	}218219	pub fn create_multiple_items(220		collection: &FungibleHandle<T>,221		sender: &T::CrossAccountId,222		data: Vec<CreateItemData<T>>,223	) -> DispatchResult {224		if !collection.is_owner_or_admin(sender) {225			ensure!(226				collection.mint_mode,227				<CommonError<T>>::PublicMintingNotAllowed228			);229			collection.check_allowlist(sender)?;230231			for (owner, _) in data.iter() {232				collection.check_allowlist(owner)?;233			}234		}235236		let mut balances = BTreeMap::new();237238		let total_supply = data239			.iter()240			.map(|u| u.1)241			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {242				acc.checked_add(v)243			})244			.ok_or(ArithmeticError::Overflow)?;245246		for (user, amount) in data.into_iter() {247			let balance = balances248				.entry(user.clone())249				.or_insert_with(|| <Balance<T>>::get((collection.id, user)));250			*balance = (*balance)251				.checked_add(amount)252				.ok_or(ArithmeticError::Overflow)?;253		}254255		// =========256257		<TotalSupply<T>>::insert(collection.id, total_supply);258		for (user, amount) in balances {259			<Balance<T>>::insert((collection.id, &user), amount);260261			collection.log(ERC20Events::Transfer {262				from: H160::default(),263				to: *user.as_eth(),264				value: amount.into(),265			});266			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(267				collection.id,268				TokenId::default(),269				user.clone(),270				amount,271			));272		}273274		Ok(())275	}276277	fn set_allowance_unchecked(278		collection: &FungibleHandle<T>,279		owner: &T::CrossAccountId,280		spender: &T::CrossAccountId,281		amount: u128,282	) {283		if amount == 0 {284			<Allowance<T>>::remove((collection.id, owner, spender));285		} else {286			<Allowance<T>>::insert((collection.id, owner, spender), amount);287		}288289		collection.log(ERC20Events::Approval {290			owner: *owner.as_eth(),291			spender: *spender.as_eth(),292			value: amount.into(),293		});294		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(295			collection.id,296			TokenId(0),297			owner.clone(),298			spender.clone(),299			amount,300		));301	}302303	pub fn set_allowance(304		collection: &FungibleHandle<T>,305		owner: &T::CrossAccountId,306		spender: &T::CrossAccountId,307		amount: u128,308	) -> DispatchResult {309		if collection.access == AccessMode::AllowList {310			collection.check_allowlist(owner)?;311			collection.check_allowlist(spender)?;312		}313314		if <Balance<T>>::get((collection.id, owner)) < amount {315			ensure!(316				collection.ignores_owned_amount(owner),317				<CommonError<T>>::CantApproveMoreThanOwned318			);319		}320321		// =========322323		Self::set_allowance_unchecked(collection, owner, spender, amount);324		Ok(())325	}326327	pub fn transfer_from(328		collection: &FungibleHandle<T>,329		spender: &T::CrossAccountId,330		from: &T::CrossAccountId,331		to: &T::CrossAccountId,332		amount: u128,333	) -> DispatchResult {334		if spender.conv_eq(from) {335			return Self::transfer(collection, from, to, amount);336		}337		if collection.access == AccessMode::AllowList {338			// `from`, `to` checked in [`transfer`]339			collection.check_allowlist(spender)?;340		}341342		let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);343		if allowance.is_none() {344			ensure!(345				collection.ignores_allowance(spender),346				<CommonError<T>>::TokenValueNotEnough347			);348		}349350		// =========351352		Self::transfer(collection, from, to, amount)?;353		if let Some(allowance) = allowance {354			Self::set_allowance_unchecked(collection, from, spender, allowance);355		}356		Ok(())357	}358359	pub fn burn_from(360		collection: &FungibleHandle<T>,361		spender: &T::CrossAccountId,362		from: &T::CrossAccountId,363		amount: u128,364	) -> DispatchResult {365		if spender.conv_eq(from) {366			return Self::burn(collection, from, amount);367		}368		if collection.access == AccessMode::AllowList {369			// `from` checked in [`burn`]370			collection.check_allowlist(spender)?;371		}372373		let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);374		if allowance.is_none() {375			ensure!(376				collection.ignores_allowance(spender),377				<CommonError<T>>::TokenValueNotEnough378			);379		}380381		// =========382383		Self::burn(collection, from, amount)?;384		if let Some(allowance) = allowance {385			Self::set_allowance_unchecked(collection, from, spender, allowance);386		}387		Ok(())388	}389390	/// Delegated to `create_multiple_items`391	pub fn create_item(392		collection: &FungibleHandle<T>,393		sender: &T::CrossAccountId,394		data: CreateItemData<T>,395	) -> DispatchResult {396		Self::create_multiple_items(collection, sender, vec![data])397	}398}
after · pallets/fungible/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::Deref;4use frame_support::{ensure};5use up_data_structs::{AccessMode, Collection, CollectionId, TokenId, CreateCollectionData};6use pallet_common::{7	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,8};9use pallet_evm_coder_substrate::WithRecorder;10use sp_core::H160;11use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};12use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};1314pub use pallet::*;1516use crate::erc::ERC20Events;17#[cfg(feature = "runtime-benchmarks")]18pub mod benchmarking;19pub mod common;20pub mod erc;21pub mod weights;2223pub type CreateItemData<T> = (<T as pallet_common::Config>::CrossAccountId, u128);24pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;2526#[frame_support::pallet]27pub mod pallet {28	use frame_support::{Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};29	use up_data_structs::CollectionId;30	use super::weights::WeightInfo;3132	#[pallet::error]33	pub enum Error<T> {34		/// Not Fungible item data used to mint in Fungible collection.35		NotFungibleDataUsedToMintFungibleCollectionToken,36		/// Not default id passed as TokenId argument37		FungibleItemsHaveNoId,38		/// Tried to set data for fungible item39		FungibleItemsDontHaveData,40	}4142	#[pallet::config]43	pub trait Config: frame_system::Config + pallet_common::Config {44		type WeightInfo: WeightInfo;45	}4647	#[pallet::pallet]48	#[pallet::generate_store(pub(super) trait Store)]49	pub struct Pallet<T>(_);5051	#[pallet::storage]52	pub type TotalSupply<T: Config> =53		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u128, QueryKind = ValueQuery>;5455	#[pallet::storage]56	pub type Balance<T: Config> = StorageNMap<57		Key = (58			Key<Twox64Concat, CollectionId>,59			Key<Blake2_128Concat, T::CrossAccountId>,60		),61		Value = u128,62		QueryKind = ValueQuery,63	>;6465	#[pallet::storage]66	pub type Allowance<T: Config> = StorageNMap<67		Key = (68			Key<Twox64Concat, CollectionId>,69			Key<Blake2_128, T::CrossAccountId>,70			Key<Blake2_128Concat, T::CrossAccountId>,71		),72		Value = u128,73		QueryKind = ValueQuery,74	>;75}7677pub struct FungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);78impl<T: Config> FungibleHandle<T> {79	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {80		Self(inner)81	}82	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {83		self.084	}85}86impl<T: Config> WithRecorder<T> for FungibleHandle<T> {87	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {88		self.0.recorder()89	}90	fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {91		self.0.into_recorder()92	}93}94impl<T: Config> Deref for FungibleHandle<T> {95	type Target = pallet_common::CollectionHandle<T>;9697	fn deref(&self) -> &Self::Target {98		&self.099	}100}101102impl<T: Config> Pallet<T> {103	pub fn init_collection(104		owner: T::AccountId,105		data: CreateCollectionData<T::AccountId>,106	) -> Result<CollectionId, DispatchError> {107		<PalletCommon<T>>::init_collection(owner, data)108	}109	pub fn destroy_collection(110		collection: FungibleHandle<T>,111		sender: &T::CrossAccountId,112	) -> DispatchResult {113		let id = collection.id;114115		// =========116117		PalletCommon::destroy_collection(collection.0, sender)?;118119		<TotalSupply<T>>::remove(id);120		<Balance<T>>::remove_prefix((id,), None);121		<Allowance<T>>::remove_prefix((id,), None);122		Ok(())123	}124125	pub fn burn(126		collection: &FungibleHandle<T>,127		owner: &T::CrossAccountId,128		amount: u128,129	) -> DispatchResult {130		let total_supply = <TotalSupply<T>>::get(collection.id)131			.checked_sub(amount)132			.ok_or(<CommonError<T>>::TokenValueTooLow)?;133134		let balance = <Balance<T>>::get((collection.id, owner))135			.checked_sub(amount)136			.ok_or(<CommonError<T>>::TokenValueTooLow)?;137138		if collection.access == AccessMode::AllowList {139			collection.check_allowlist(owner)?;140		}141142		// =========143144		if balance == 0 {145			<Balance<T>>::remove((collection.id, owner));146		} else {147			<Balance<T>>::insert((collection.id, owner), balance);148		}149		<TotalSupply<T>>::insert(collection.id, total_supply);150151		collection.log(ERC20Events::Transfer {152			from: *owner.as_eth(),153			to: H160::default(),154			value: amount.into(),155		});156		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(157			collection.id,158			TokenId::default(),159			owner.clone(),160			amount,161		));162		Ok(())163	}164165	pub fn transfer(166		collection: &FungibleHandle<T>,167		from: &T::CrossAccountId,168		to: &T::CrossAccountId,169		amount: u128,170	) -> DispatchResult {171		ensure!(172			collection.limits.transfers_enabled(),173			<CommonError<T>>::TransferNotAllowed,174		);175176		if collection.access == AccessMode::AllowList {177			collection.check_allowlist(from)?;178			collection.check_allowlist(to)?;179		}180		<PalletCommon<T>>::ensure_correct_receiver(to)?;181182		let balance_from = <Balance<T>>::get((collection.id, from))183			.checked_sub(amount)184			.ok_or(<CommonError<T>>::TokenValueTooLow)?;185		let balance_to = if from != to {186			Some(187				<Balance<T>>::get((collection.id, to))188					.checked_add(amount)189					.ok_or(ArithmeticError::Overflow)?,190			)191		} else {192			None193		};194195		// =========196197		if let Some(balance_to) = balance_to {198			// from != to199			if balance_from == 0 {200				<Balance<T>>::remove((collection.id, from));201			} else {202				<Balance<T>>::insert((collection.id, from), balance_from);203			}204			<Balance<T>>::insert((collection.id, to), balance_to);205		}206207		collection.log(ERC20Events::Transfer {208			from: *from.as_eth(),209			to: *to.as_eth(),210			value: amount.into(),211		});212		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(213			collection.id,214			TokenId::default(),215			from.clone(),216			to.clone(),217			amount,218		));219		Ok(())220	}221222	pub fn create_multiple_items(223		collection: &FungibleHandle<T>,224		sender: &T::CrossAccountId,225		data: Vec<CreateItemData<T>>,226	) -> DispatchResult {227		if !collection.is_owner_or_admin(sender) {228			ensure!(229				collection.mint_mode,230				<CommonError<T>>::PublicMintingNotAllowed231			);232			collection.check_allowlist(sender)?;233234			for (owner, _) in data.iter() {235				collection.check_allowlist(owner)?;236			}237		}238239		let mut balances = BTreeMap::new();240241		let total_supply = data242			.iter()243			.map(|u| u.1)244			.try_fold(<TotalSupply<T>>::get(collection.id), |acc, v| {245				acc.checked_add(v)246			})247			.ok_or(ArithmeticError::Overflow)?;248249		for (user, amount) in data.into_iter() {250			let balance = balances251				.entry(user.clone())252				.or_insert_with(|| <Balance<T>>::get((collection.id, user)));253			*balance = (*balance)254				.checked_add(amount)255				.ok_or(ArithmeticError::Overflow)?;256		}257258		// =========259260		<TotalSupply<T>>::insert(collection.id, total_supply);261		for (user, amount) in balances {262			<Balance<T>>::insert((collection.id, &user), amount);263264			collection.log(ERC20Events::Transfer {265				from: H160::default(),266				to: *user.as_eth(),267				value: amount.into(),268			});269			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(270				collection.id,271				TokenId::default(),272				user.clone(),273				amount,274			));275		}276277		Ok(())278	}279280	fn set_allowance_unchecked(281		collection: &FungibleHandle<T>,282		owner: &T::CrossAccountId,283		spender: &T::CrossAccountId,284		amount: u128,285	) {286		if amount == 0 {287			<Allowance<T>>::remove((collection.id, owner, spender));288		} else {289			<Allowance<T>>::insert((collection.id, owner, spender), amount);290		}291292		collection.log(ERC20Events::Approval {293			owner: *owner.as_eth(),294			spender: *spender.as_eth(),295			value: amount.into(),296		});297		<PalletCommon<T>>::deposit_event(CommonEvent::Approved(298			collection.id,299			TokenId(0),300			owner.clone(),301			spender.clone(),302			amount,303		));304	}305306	pub fn set_allowance(307		collection: &FungibleHandle<T>,308		owner: &T::CrossAccountId,309		spender: &T::CrossAccountId,310		amount: u128,311	) -> DispatchResult {312		if collection.access == AccessMode::AllowList {313			collection.check_allowlist(owner)?;314			collection.check_allowlist(spender)?;315		}316317		if <Balance<T>>::get((collection.id, owner)) < amount {318			ensure!(319				collection.ignores_owned_amount(owner),320				<CommonError<T>>::CantApproveMoreThanOwned321			);322		}323324		// =========325326		Self::set_allowance_unchecked(collection, owner, spender, amount);327		Ok(())328	}329330	pub fn transfer_from(331		collection: &FungibleHandle<T>,332		spender: &T::CrossAccountId,333		from: &T::CrossAccountId,334		to: &T::CrossAccountId,335		amount: u128,336	) -> DispatchResult {337		if spender.conv_eq(from) {338			return Self::transfer(collection, from, to, amount);339		}340		if collection.access == AccessMode::AllowList {341			// `from`, `to` checked in [`transfer`]342			collection.check_allowlist(spender)?;343		}344345		let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);346		if allowance.is_none() {347			ensure!(348				collection.ignores_allowance(spender),349				<CommonError<T>>::TokenValueNotEnough350			);351		}352353		// =========354355		Self::transfer(collection, from, to, amount)?;356		if let Some(allowance) = allowance {357			Self::set_allowance_unchecked(collection, from, spender, allowance);358		}359		Ok(())360	}361362	pub fn burn_from(363		collection: &FungibleHandle<T>,364		spender: &T::CrossAccountId,365		from: &T::CrossAccountId,366		amount: u128,367	) -> DispatchResult {368		if spender.conv_eq(from) {369			return Self::burn(collection, from, amount);370		}371		if collection.access == AccessMode::AllowList {372			// `from` checked in [`burn`]373			collection.check_allowlist(spender)?;374		}375376		let allowance = <Allowance<T>>::get((collection.id, from, spender)).checked_sub(amount);377		if allowance.is_none() {378			ensure!(379				collection.ignores_allowance(spender),380				<CommonError<T>>::TokenValueNotEnough381			);382		}383384		// =========385386		Self::burn(collection, from, amount)?;387		if let Some(allowance) = allowance {388			Self::set_allowance_unchecked(collection, from, spender, allowance);389		}390		Ok(())391	}392393	/// Delegated to `create_multiple_items`394	pub fn create_item(395		collection: &FungibleHandle<T>,396		sender: &T::CrossAccountId,397		data: CreateItemData<T>,398	) -> DispatchResult {399		Self::create_multiple_items(collection, sender, vec![data])400	}401}
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -4,6 +4,7 @@
 use frame_support::{BoundedVec, ensure};
 use up_data_structs::{
 	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,
+	CreateCollectionData,
 };
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
@@ -142,8 +143,11 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: NonfungibleHandle<T>,
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -3,7 +3,7 @@
 use frame_support::{ensure, BoundedVec};
 use up_data_structs::{
 	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit,
-	MAX_REFUNGIBLE_PIECES, TokenId,
+	MAX_REFUNGIBLE_PIECES, TokenId, CreateCollectionData,
 };
 use pallet_common::{
 	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId,
@@ -156,8 +156,11 @@
 
 // unchecked calls skips any permission checks
 impl<T: Config> Pallet<T> {
-	pub fn init_collection(data: Collection<T::AccountId>) -> Result<CollectionId, DispatchError> {
-		<PalletCommon<T>>::init_collection(data)
+	pub fn init_collection(
+		owner: T::AccountId,
+		data: CreateCollectionData<T::AccountId>,
+	) -> Result<CollectionId, DispatchError> {
+		<PalletCommon<T>>::init_collection(owner, data)
 	}
 	pub fn destroy_collection(
 		collection: RefungibleHandle<T>,
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -35,11 +35,10 @@
 use frame_system::{self as system, ensure_signed};
 use sp_runtime::{sp_std::prelude::Vec};
 use up_data_structs::{
-	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT,
-	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT,
-	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
-	NFT_SPONSOR_TRANSFER_TIMEOUT, AccessMode, Collection, CreateItemData, CollectionLimits,
-	CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
+	MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT,
+	OFFCHAIN_SCHEMA_LIMIT, AccessMode, CreateItemData, CollectionLimits, CollectionId,
+	CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission,
+	CreateCollectionData,
 };
 use pallet_common::{
 	account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError,
@@ -81,10 +80,6 @@
 		ConfirmUnsetSponsorFail,
 		/// Length of items properties must be greater than 0.
 		EmptyArgument,
-		/// Collection limit bounds per collection exceeded
-		CollectionLimitBoundsExceeded,
-		/// Tried to enable permissions which are only permitted to be disabled
-		OwnerPermissionsCantBeReverted,
 	}
 }
 
@@ -318,42 +313,38 @@
 		// returns collection ID
 		#[weight = <SelfWeightOf<T>>::create_collection()]
 		#[transactional]
+		#[deprecated]
 		pub fn create_collection(origin,
 								 collection_name: Vec<u16>,
 								 collection_description: Vec<u16>,
 								 token_prefix: Vec<u8>,
 								 mode: CollectionMode) -> DispatchResult {
-
-			// Anyone can create a collection
-			let who = ensure_signed(origin)?;
-
-			// Create new collection
-			let new_collection = Collection {
-				owner: who,
+			Self::create_collection_ex(origin, CreateCollectionData {
 				name: collection_name,
-				mode: mode.clone(),
-				mint_mode: false,
-				access: AccessMode::Normal,
 				description: collection_description,
 				token_prefix,
-				offchain_schema: Vec::new(),
-				schema_version: SchemaVersion::ImageURL,
-				sponsorship: SponsorshipState::Disabled,
-				variable_on_chain_schema: Vec::new(),
-				const_on_chain_schema: Vec::new(),
-				limits: Default::default(),
-				meta_update_permission: Default::default(),
-			};
+				mode,
+				..Default::default()
+			})
+		}
+
+		/// This method creates a collection
+		///
+		/// Prefer it to deprecated [`created_collection`] method
+		#[weight = <SelfWeightOf<T>>::create_collection()]
+		#[transactional]
+		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {
+			let owner = ensure_signed(origin)?;
 
-			let _id = match mode {
-				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(new_collection)?},
+			let _id = match data.mode {
+				CollectionMode::NFT => {<PalletNonfungible<T>>::init_collection(owner, data)?},
 				CollectionMode::Fungible(decimal_points) => {
 					// check params
 					ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);
-					<PalletFungible<T>>::init_collection(new_collection)?
+					<PalletFungible<T>>::init_collection(owner, data)?
 				}
 				CollectionMode::ReFungible => {
-					<PalletRefungible<T>>::init_collection(new_collection)?
+					<PalletRefungible<T>>::init_collection(owner, data)?
 				}
 			};
 
@@ -1099,61 +1090,12 @@
 			collection_id: CollectionId,
 			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_limit = &target_collection.limits;
 
-			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
-						}
-					)*
-				}};
-			}
-
-			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_limit;
+			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;
 
 			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(
 				collection_id
modifiedpallets/unique/src/tests.rsdiffbeforeafterboth
--- a/pallets/unique/src/tests.rs
+++ b/pallets/unique/src/tests.rs
@@ -5,7 +5,7 @@
 use up_data_structs::{
 	COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData,
 	CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission,
-	TokenId,
+	TokenId, MAX_TOKEN_OWNERSHIP,
 };
 use frame_support::{assert_noop, assert_ok};
 use sp_std::convert::TryInto;
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -230,6 +230,25 @@
 	pub meta_update_permission: MetaUpdatePermission,
 }
 
+#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+#[derivative(Default)]
+pub struct CreateCollectionData<AccountId> {
+	#[derivative(Default(value = "CollectionMode::NFT"))]
+	pub mode: CollectionMode,
+	pub access: Option<AccessMode>,
+	pub name: Vec<u16>,
+	pub description: Vec<u16>,
+	pub token_prefix: Vec<u8>,
+	pub offchain_schema: Vec<u8>,
+	pub schema_version: Option<SchemaVersion>,
+	pub pending_sponsor: Option<AccountId>,
+	pub limits: Option<CollectionLimits>,
+	pub variable_on_chain_schema: Vec<u8>,
+	pub const_on_chain_schema: Vec<u8>,
+	pub meta_update_permission: Option<MetaUpdatePermission>,
+}
+
 #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct NftItemType<AccountId> {