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

difftreelog

feat track and limit admin amount

Yaroslav Bolyukin2021-11-04parent: #98884c8.patch.diff
in: master

3 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
before · pallets/common/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use sp_std::vec::Vec;5use account::CrossAccountId;6use frame_support::{7	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},8	ensure, fail,9	traits::{Imbalance, Get, Currency},10};11use nft_data_structs::{12	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,13	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,14	MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight, WithdrawReasons,15};16pub use pallet::*;17use sp_core::H160;18use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};19pub mod account;20#[cfg(feature = "runtime-benchmarks")]21pub mod benchmarking;22pub mod erc;23pub mod eth;2425#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]26pub struct CollectionHandle<T: Config> {27	pub id: CollectionId,28	collection: Collection<T>,29	pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,30}31impl<T: Config> CollectionHandle<T> {32	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {33		<CollectionById<T>>::get(id).map(|collection| Self {34			id,35			collection,36			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(37				eth::collection_id_to_address(id),38				gas_limit,39			),40		})41	}42	pub fn new(id: CollectionId) -> Option<Self> {43		Self::new_with_gas_limit(id, u64::MAX)44	}45	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {46		Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)47	}48	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {49		self.recorder.log_sub(log)50	}51	pub fn log_infallible(&self, log: impl evm_coder::ToLog) {52		self.recorder.log_infallible(log)53	}54	#[allow(dead_code)]55	fn consume_gas(&self, gas: u64) -> DispatchResult {56		self.recorder.consume_gas_sub(gas)57	}58	pub fn consume_sload(&self) -> DispatchResult {59		self.recorder.consume_sload_sub()60	}61	pub fn consume_sstores(&self, amount: usize) -> DispatchResult {62		self.recorder.consume_sstores_sub(amount)63	}64	pub fn consume_sstore(&self) -> DispatchResult {65		self.recorder.consume_sstore_sub()66	}67	pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {68		self.recorder.consume_log_sub(topics, data)69	}70	pub fn submit_logs(self) -> DispatchResult {71		self.recorder.submit_logs()72	}73	pub fn save(self) -> DispatchResult {74		self.recorder.submit_logs()?;75		<CollectionById<T>>::insert(self.id, self.collection);76		Ok(())77	}78}79impl<T: Config> Deref for CollectionHandle<T> {80	type Target = Collection<T>;8182	fn deref(&self) -> &Self::Target {83		&self.collection84	}85}8687impl<T: Config> DerefMut for CollectionHandle<T> {88	fn deref_mut(&mut self) -> &mut Self::Target {89		&mut self.collection90	}91}9293impl<T: Config> CollectionHandle<T> {94	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {95		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);96		Ok(())97	}98	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {99		self.consume_sload()?;100101		Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject)))102	}103	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {104		ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);105		Ok(())106	}107	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {108		Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)109	}110	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {111		Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)112	}113	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {114		self.consume_sload()?;115116		ensure!(117			<Allowlist<T>>::get((self.id, user)),118			<Error<T>>::AddressNotInAllowlist119		);120		Ok(())121	}122123	pub fn check_can_update_meta(124		&self,125		subject: &T::CrossAccountId,126		item_owner: &T::CrossAccountId,127	) -> DispatchResult {128		match self.meta_update_permission {129			MetaUpdatePermission::ItemOwner => {130				ensure!(subject == item_owner, <Error<T>>::NoPermission);131				Ok(())132			}133			MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),134			MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),135		}136	}137}138139#[frame_support::pallet]140pub mod pallet {141	use super::*;142	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};143	use account::{EvmBackwardsAddressMapping, CrossAccountId};144	use frame_support::traits::Currency;145	use nft_data_structs::TokenId;146	use scale_info::TypeInfo;147148	#[pallet::config]149	pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {150		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;151152		type CrossAccountId: CrossAccountId<Self::AccountId>;153154		type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;155		type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;156157		type Currency: Currency<Self::AccountId>;158		type CollectionCreationPrice: Get<159			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,160		>;161		type TreasuryAccountId: Get<Self::AccountId>;162	}163164	#[pallet::pallet]165	#[pallet::generate_store(pub(super) trait Store)]166	pub struct Pallet<T>(_);167168	#[pallet::event]169	#[pallet::generate_deposit(pub fn deposit_event)]170	pub enum Event<T: Config> {171		/// New collection was created172		///173		/// # Arguments174		///175		/// * collection_id: Globally unique identifier of newly created collection.176		///177		/// * mode: [CollectionMode] converted into u8.178		///179		/// * account_id: Collection owner.180		CollectionCreated(CollectionId, u8, T::AccountId),181182		/// New item was created.183		///184		/// # Arguments185		///186		/// * collection_id: Id of the collection where item was created.187		///188		/// * item_id: Id of an item. Unique within the collection.189		///190		/// * recipient: Owner of newly created item191		///192		/// * amount: Always 1 for NFT193		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),194195		/// Collection item was burned.196		///197		/// # Arguments198		///199		/// * collection_id.200		///201		/// * item_id: Identifier of burned NFT.202		///203		/// * owner: which user has destroyed its tokens204		///205		/// * amount: Always 1 for NFT206		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),207208		/// Item was transferred209		///210		/// * collection_id: Id of collection to which item is belong211		///212		/// * item_id: Id of an item213		///214		/// * sender: Original owner of item215		///216		/// * recipient: New owner of item217		///218		/// * amount: Always 1 for NFT219		Transfer(220			CollectionId,221			TokenId,222			T::CrossAccountId,223			T::CrossAccountId,224			u128,225		),226227		/// * collection_id228		///229		/// * item_id230		///231		/// * sender232		///233		/// * spender234		///235		/// * amount236		Approved(237			CollectionId,238			TokenId,239			T::CrossAccountId,240			T::CrossAccountId,241			u128,242		),243	}244245	#[pallet::error]246	pub enum Error<T> {247		/// This collection does not exist.248		CollectionNotFound,249		/// Sender parameter and item owner must be equal.250		MustBeTokenOwner,251		/// No permission to perform action252		NoPermission,253		/// Collection is not in mint mode.254		PublicMintingNotAllowed,255		/// Address is not in white list.256		AddressNotInAllowlist,257258		/// Collection name can not be longer than 63 char.259		CollectionNameLimitExceeded,260		/// Collection description can not be longer than 255 char.261		CollectionDescriptionLimitExceeded,262		/// Token prefix can not be longer than 15 char.263		CollectionTokenPrefixLimitExceeded,264		/// Total collections bound exceeded.265		TotalCollectionsLimitExceeded,266		/// variable_data exceeded data limit.267		TokenVariableDataLimitExceeded,268269		/// Collection settings not allowing items transferring270		TransferNotAllowed,271		/// Account token limit exceeded per collection272		AccountTokenLimitExceeded,273		/// Collection token limit exceeded274		CollectionTokenLimitExceeded,275		/// Metadata flag frozen276		MetadataFlagFrozen,277278		/// Item not exists.279		TokenNotFound,280		/// Item balance not enough.281		TokenValueTooLow,282		/// Requested value more than approved.283		TokenValueNotEnough,284		/// Tried to approve more than owned285		CantApproveMoreThanOwned,286287		/// Can't transfer tokens to ethereum zero address288		AddressIsZero,289		/// Target collection doesn't supports this operation290		UnsupportedOperation,291	}292293	#[pallet::storage]294	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;295	#[pallet::storage]296	pub type DestroyedCollectionCount<T> =297		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;298299	/// Collection info300	#[pallet::storage]301	pub type CollectionById<T> = StorageMap<302		Hasher = Blake2_128Concat,303		Key = CollectionId,304		Value = Collection<T>,305		QueryKind = OptionQuery,306	>;307308	/// List of collection admins309	#[pallet::storage]310	pub type IsAdmin<T: Config> = StorageNMap<311		Key = (312			Key<Blake2_128Concat, CollectionId>,313			Key<Blake2_128Concat, T::CrossAccountId>,314		),315		Value = bool,316		QueryKind = ValueQuery,317	>;318319	/// Allowlisted collection users320	#[pallet::storage]321	pub type Allowlist<T: Config> = StorageNMap<322		Key = (323			Key<Blake2_128Concat, CollectionId>,324			Key<Blake2_128Concat, T::CrossAccountId>,325		),326		Value = bool,327		QueryKind = ValueQuery,328	>;329}330331impl<T: Config> Pallet<T> {332	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens333	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {334		ensure!(335			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,336			<Error<T>>::AddressIsZero337		);338		Ok(())339	}340}341342impl<T: Config> Pallet<T> {343	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {344		{345			ensure!(346				data.name.len() <= MAX_COLLECTION_NAME_LENGTH,347				Error::<T>::CollectionNameLimitExceeded348			);349			ensure!(350				data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,351				Error::<T>::CollectionDescriptionLimitExceeded352			);353			ensure!(354				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,355				Error::<T>::CollectionTokenPrefixLimitExceeded356			);357		}358359		let created_count = <CreatedCollectionCount<T>>::get()360			.0361			.checked_add(1)362			.ok_or(ArithmeticError::Overflow)?;363		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;364		let id = CollectionId(created_count);365366		// bound Total number of collections367		ensure!(368			created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,369			<Error<T>>::TotalCollectionsLimitExceeded370		);371372		// =========373374		// Take a (non-refundable) deposit of collection creation375		{376			let mut imbalance =377				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();378			imbalance.subsume(379				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(380					&T::TreasuryAccountId::get(),381					T::CollectionCreationPrice::get(),382				),383			);384			<T as Config>::Currency::settle(385				&data.owner,386				imbalance,387				WithdrawReasons::TRANSFER,388				ExistenceRequirement::KeepAlive,389			)390			.map_err(|_| Error::<T>::NoPermission)?;391		}392393		<CreatedCollectionCount<T>>::put(created_count);394		<Pallet<T>>::deposit_event(Event::CollectionCreated(395			id,396			data.mode.id(),397			data.owner.clone(),398		));399		<CollectionById<T>>::insert(id, data);400		Ok(id)401	}402403	pub fn destroy_collection(404		collection: CollectionHandle<T>,405		sender: &T::CrossAccountId,406	) -> DispatchResult {407		ensure!(408			collection.limits.owner_can_destroy(),409			<Error<T>>::NoPermission,410		);411		collection.check_is_owner(&sender)?;412413		let destroyed_collections = <DestroyedCollectionCount<T>>::get()414			.0415			.checked_add(1)416			.ok_or(ArithmeticError::Overflow)?;417418		// =========419420		<DestroyedCollectionCount<T>>::put(destroyed_collections);421		<CollectionById<T>>::remove(collection.id);422		<IsAdmin<T>>::remove_prefix((collection.id,), None);423		<Allowlist<T>>::remove_prefix((collection.id,), None);424		Ok(())425	}426427	pub fn toggle_allowlist(428		collection: &CollectionHandle<T>,429		sender: &T::CrossAccountId,430		user: &T::CrossAccountId,431		allowed: bool,432	) -> DispatchResult {433		collection.check_is_owner_or_admin(&sender)?;434435		// =========436437		if allowed {438			<Allowlist<T>>::insert((collection.id, user.as_sub()), true);439		} else {440			<Allowlist<T>>::remove((collection.id, user.as_sub()));441		}442443		Ok(())444	}445}446447#[macro_export]448macro_rules! unsupported {449	() => {450		Err(<Error<T>>::UnsupportedOperation.into())451	};452}453454/// Worst cases455pub trait CommonWeightInfo {456	fn create_item() -> Weight;457	fn create_multiple_items(amount: u32) -> Weight;458	fn burn_item() -> Weight;459	fn transfer() -> Weight;460	fn approve() -> Weight;461	fn transfer_from() -> Weight;462	fn burn_from() -> Weight;463	fn set_variable_metadata(bytes: u32) -> Weight;464}465466pub trait CommonCollectionOperations<T: Config> {467	fn create_item(468		&self,469		sender: T::CrossAccountId,470		to: T::CrossAccountId,471		data: CreateItemData,472	) -> DispatchResultWithPostInfo;473	fn create_multiple_items(474		&self,475		sender: T::CrossAccountId,476		to: T::CrossAccountId,477		data: Vec<CreateItemData>,478	) -> DispatchResultWithPostInfo;479	fn burn_item(480		&self,481		sender: T::CrossAccountId,482		token: TokenId,483		amount: u128,484	) -> DispatchResultWithPostInfo;485486	fn transfer(487		&self,488		sender: T::CrossAccountId,489		to: T::CrossAccountId,490		token: TokenId,491		amount: u128,492	) -> DispatchResultWithPostInfo;493	fn approve(494		&self,495		sender: T::CrossAccountId,496		spender: T::CrossAccountId,497		token: TokenId,498		amount: u128,499	) -> DispatchResultWithPostInfo;500	fn transfer_from(501		&self,502		sender: T::CrossAccountId,503		from: T::CrossAccountId,504		to: T::CrossAccountId,505		token: TokenId,506		amount: u128,507	) -> DispatchResultWithPostInfo;508	fn burn_from(509		&self,510		sender: T::CrossAccountId,511		from: T::CrossAccountId,512		token: TokenId,513		amount: u128,514	) -> DispatchResultWithPostInfo;515516	fn set_variable_metadata(517		&self,518		sender: T::CrossAccountId,519		token: TokenId,520		data: Vec<u8>,521	) -> DispatchResultWithPostInfo;522523	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;524	fn token_exists(&self, token: TokenId) -> bool;525	fn last_token_id(&self) -> TokenId;526527	fn token_owner(&self, token: TokenId) -> T::CrossAccountId;528	fn const_metadata(&self, token: TokenId) -> Vec<u8>;529	fn variable_metadata(&self, token: TokenId) -> Vec<u8>;530531	/// How many tokens collection contains (Applicable to nonfungible/refungible)532	fn collection_tokens(&self) -> u32;533	/// Amount of different tokens account has (Applicable to nonfungible/refungible)534	fn account_balance(&self, account: T::CrossAccountId) -> u32;535	/// Amount of specific token account have (Applicable to fungible/refungible)536	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;537	fn allowance(538		&self,539		sender: T::CrossAccountId,540		spender: T::CrossAccountId,541		token: TokenId,542	) -> u128;543}544545// Flexible enough for implementing CommonCollectionOperations546pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {547	let post_info = PostDispatchInfo {548		actual_weight: Some(weight),549		pays_fee: Pays::Yes,550	};551	match res {552		Ok(()) => Ok(post_info),553		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),554	}555}
after · pallets/common/src/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]23use core::ops::{Deref, DerefMut};4use sp_std::vec::Vec;5use account::CrossAccountId;6use frame_support::{7	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo},8	ensure, fail,9	traits::{Imbalance, Get, Currency},10};11use nft_data_structs::{12	COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement,13	MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_TOKEN_PREFIX_LENGTH,14	COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, TokenId, Weight,15	WithdrawReasons,16};17pub use pallet::*;18use sp_core::H160;19use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};20pub mod account;21#[cfg(feature = "runtime-benchmarks")]22pub mod benchmarking;23pub mod erc;24pub mod eth;2526#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]27pub struct CollectionHandle<T: Config> {28	pub id: CollectionId,29	collection: Collection<T>,30	pub recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,31}32impl<T: Config> CollectionHandle<T> {33	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {34		<CollectionById<T>>::get(id).map(|collection| Self {35			id,36			collection,37			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(38				eth::collection_id_to_address(id),39				gas_limit,40			),41		})42	}43	pub fn new(id: CollectionId) -> Option<Self> {44		Self::new_with_gas_limit(id, u64::MAX)45	}46	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {47		Ok(Self::new(id).ok_or_else(|| <Error<T>>::CollectionNotFound)?)48	}49	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {50		self.recorder.log_sub(log)51	}52	pub fn log_infallible(&self, log: impl evm_coder::ToLog) {53		self.recorder.log_infallible(log)54	}55	#[allow(dead_code)]56	fn consume_gas(&self, gas: u64) -> DispatchResult {57		self.recorder.consume_gas_sub(gas)58	}59	pub fn consume_sload(&self) -> DispatchResult {60		self.recorder.consume_sload_sub()61	}62	pub fn consume_sstores(&self, amount: usize) -> DispatchResult {63		self.recorder.consume_sstores_sub(amount)64	}65	pub fn consume_sstore(&self) -> DispatchResult {66		self.recorder.consume_sstore_sub()67	}68	pub fn consume_log(&self, topics: usize, data: usize) -> DispatchResult {69		self.recorder.consume_log_sub(topics, data)70	}71	pub fn submit_logs(self) -> DispatchResult {72		self.recorder.submit_logs()73	}74	pub fn save(self) -> DispatchResult {75		self.recorder.submit_logs()?;76		<CollectionById<T>>::insert(self.id, self.collection);77		Ok(())78	}79}80impl<T: Config> Deref for CollectionHandle<T> {81	type Target = Collection<T>;8283	fn deref(&self) -> &Self::Target {84		&self.collection85	}86}8788impl<T: Config> DerefMut for CollectionHandle<T> {89	fn deref_mut(&mut self) -> &mut Self::Target {90		&mut self.collection91	}92}9394impl<T: Config> CollectionHandle<T> {95	pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {96		ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);97		Ok(())98	}99	pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> Result<bool, DispatchError> {100		self.consume_sload()?;101102		Ok(*subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject)))103	}104	pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {105		ensure!(self.is_owner_or_admin(subject)?, <Error<T>>::NoPermission);106		Ok(())107	}108	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {109		Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)110	}111	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> Result<bool, DispatchError> {112		Ok(self.limits.owner_can_transfer() && self.is_owner_or_admin(user)?)113	}114	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {115		self.consume_sload()?;116117		ensure!(118			<Allowlist<T>>::get((self.id, user)),119			<Error<T>>::AddressNotInAllowlist120		);121		Ok(())122	}123124	pub fn check_can_update_meta(125		&self,126		subject: &T::CrossAccountId,127		item_owner: &T::CrossAccountId,128	) -> DispatchResult {129		match self.meta_update_permission {130			MetaUpdatePermission::ItemOwner => {131				ensure!(subject == item_owner, <Error<T>>::NoPermission);132				Ok(())133			}134			MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),135			MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),136		}137	}138}139140#[frame_support::pallet]141pub mod pallet {142	use super::*;143	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};144	use account::{EvmBackwardsAddressMapping, CrossAccountId};145	use frame_support::traits::Currency;146	use nft_data_structs::TokenId;147	use scale_info::TypeInfo;148149	#[pallet::config]150	pub trait Config: frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo {151		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;152153		type CrossAccountId: CrossAccountId<Self::AccountId>;154155		type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;156		type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;157158		type Currency: Currency<Self::AccountId>;159		type CollectionCreationPrice: Get<160			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,161		>;162		type TreasuryAccountId: Get<Self::AccountId>;163	}164165	#[pallet::pallet]166	#[pallet::generate_store(pub(super) trait Store)]167	pub struct Pallet<T>(_);168169	#[pallet::extra_constants]170	impl<T: Config> Pallet<T> {171		pub fn collection_admins_limit() -> u32 {172			COLLECTION_ADMINS_LIMIT173		}174	}175176	#[pallet::event]177	#[pallet::generate_deposit(pub fn deposit_event)]178	pub enum Event<T: Config> {179		/// New collection was created180		///181		/// # Arguments182		///183		/// * collection_id: Globally unique identifier of newly created collection.184		///185		/// * mode: [CollectionMode] converted into u8.186		///187		/// * account_id: Collection owner.188		CollectionCreated(CollectionId, u8, T::AccountId),189190		/// New item was created.191		///192		/// # Arguments193		///194		/// * collection_id: Id of the collection where item was created.195		///196		/// * item_id: Id of an item. Unique within the collection.197		///198		/// * recipient: Owner of newly created item199		///200		/// * amount: Always 1 for NFT201		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),202203		/// Collection item was burned.204		///205		/// # Arguments206		///207		/// * collection_id.208		///209		/// * item_id: Identifier of burned NFT.210		///211		/// * owner: which user has destroyed its tokens212		///213		/// * amount: Always 1 for NFT214		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),215216		/// Item was transferred217		///218		/// * collection_id: Id of collection to which item is belong219		///220		/// * item_id: Id of an item221		///222		/// * sender: Original owner of item223		///224		/// * recipient: New owner of item225		///226		/// * amount: Always 1 for NFT227		Transfer(228			CollectionId,229			TokenId,230			T::CrossAccountId,231			T::CrossAccountId,232			u128,233		),234235		/// * collection_id236		///237		/// * item_id238		///239		/// * sender240		///241		/// * spender242		///243		/// * amount244		Approved(245			CollectionId,246			TokenId,247			T::CrossAccountId,248			T::CrossAccountId,249			u128,250		),251	}252253	#[pallet::error]254	pub enum Error<T> {255		/// This collection does not exist.256		CollectionNotFound,257		/// Sender parameter and item owner must be equal.258		MustBeTokenOwner,259		/// No permission to perform action260		NoPermission,261		/// Collection is not in mint mode.262		PublicMintingNotAllowed,263		/// Address is not in white list.264		AddressNotInAllowlist,265266		/// Collection name can not be longer than 63 char.267		CollectionNameLimitExceeded,268		/// Collection description can not be longer than 255 char.269		CollectionDescriptionLimitExceeded,270		/// Token prefix can not be longer than 15 char.271		CollectionTokenPrefixLimitExceeded,272		/// Total collections bound exceeded.273		TotalCollectionsLimitExceeded,274		/// variable_data exceeded data limit.275		TokenVariableDataLimitExceeded,276		/// Exceeded max admin amount277		CollectionAdminAmountExceeded,278279		/// Collection settings not allowing items transferring280		TransferNotAllowed,281		/// Account token limit exceeded per collection282		AccountTokenLimitExceeded,283		/// Collection token limit exceeded284		CollectionTokenLimitExceeded,285		/// Metadata flag frozen286		MetadataFlagFrozen,287288		/// Item not exists.289		TokenNotFound,290		/// Item balance not enough.291		TokenValueTooLow,292		/// Requested value more than approved.293		TokenValueNotEnough,294		/// Tried to approve more than owned295		CantApproveMoreThanOwned,296297		/// Can't transfer tokens to ethereum zero address298		AddressIsZero,299		/// Target collection doesn't supports this operation300		UnsupportedOperation,301	}302303	#[pallet::storage]304	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;305	#[pallet::storage]306	pub type DestroyedCollectionCount<T> =307		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;308309	/// Collection info310	#[pallet::storage]311	pub type CollectionById<T> = StorageMap<312		Hasher = Blake2_128Concat,313		Key = CollectionId,314		Value = Collection<T>,315		QueryKind = OptionQuery,316	>;317318	#[pallet::storage]319	pub type AdminAmount<T> = StorageMap<320		Hasher = Blake2_128Concat,321		Key = CollectionId,322		Value = u32,323		QueryKind = ValueQuery,324	>;325326	/// List of collection admins327	#[pallet::storage]328	pub type IsAdmin<T: Config> = StorageNMap<329		Key = (330			Key<Blake2_128Concat, CollectionId>,331			Key<Blake2_128Concat, T::CrossAccountId>,332		),333		Value = bool,334		QueryKind = ValueQuery,335	>;336337	/// Allowlisted collection users338	#[pallet::storage]339	pub type Allowlist<T: Config> = StorageNMap<340		Key = (341			Key<Blake2_128Concat, CollectionId>,342			Key<Blake2_128Concat, T::CrossAccountId>,343		),344		Value = bool,345		QueryKind = ValueQuery,346	>;347}348349impl<T: Config> Pallet<T> {350	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens351	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {352		ensure!(353			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,354			<Error<T>>::AddressIsZero355		);356		Ok(())357	}358}359360impl<T: Config> Pallet<T> {361	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {362		{363			ensure!(364				data.name.len() <= MAX_COLLECTION_NAME_LENGTH,365				Error::<T>::CollectionNameLimitExceeded366			);367			ensure!(368				data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,369				Error::<T>::CollectionDescriptionLimitExceeded370			);371			ensure!(372				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,373				Error::<T>::CollectionTokenPrefixLimitExceeded374			);375		}376377		let created_count = <CreatedCollectionCount<T>>::get()378			.0379			.checked_add(1)380			.ok_or(ArithmeticError::Overflow)?;381		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;382		let id = CollectionId(created_count);383384		// bound Total number of collections385		ensure!(386			created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,387			<Error<T>>::TotalCollectionsLimitExceeded388		);389390		// =========391392		// Take a (non-refundable) deposit of collection creation393		{394			let mut imbalance =395				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();396			imbalance.subsume(397				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(398					&T::TreasuryAccountId::get(),399					T::CollectionCreationPrice::get(),400				),401			);402			<T as Config>::Currency::settle(403				&data.owner,404				imbalance,405				WithdrawReasons::TRANSFER,406				ExistenceRequirement::KeepAlive,407			)408			.map_err(|_| Error::<T>::NoPermission)?;409		}410411		<CreatedCollectionCount<T>>::put(created_count);412		<Pallet<T>>::deposit_event(Event::CollectionCreated(413			id,414			data.mode.id(),415			data.owner.clone(),416		));417		<CollectionById<T>>::insert(id, data);418		Ok(id)419	}420421	pub fn destroy_collection(422		collection: CollectionHandle<T>,423		sender: &T::CrossAccountId,424	) -> DispatchResult {425		ensure!(426			collection.limits.owner_can_destroy(),427			<Error<T>>::NoPermission,428		);429		collection.check_is_owner(&sender)?;430431		let destroyed_collections = <DestroyedCollectionCount<T>>::get()432			.0433			.checked_add(1)434			.ok_or(ArithmeticError::Overflow)?;435436		// =========437438		<DestroyedCollectionCount<T>>::put(destroyed_collections);439		<CollectionById<T>>::remove(collection.id);440		<AdminAmount<T>>::remove(collection.id);441		<IsAdmin<T>>::remove_prefix((collection.id,), None);442		<Allowlist<T>>::remove_prefix((collection.id,), None);443		Ok(())444	}445446	pub fn toggle_allowlist(447		collection: &CollectionHandle<T>,448		sender: &T::CrossAccountId,449		user: &T::CrossAccountId,450		allowed: bool,451	) -> DispatchResult {452		collection.check_is_owner_or_admin(&sender)?;453454		// =========455456		if allowed {457			<Allowlist<T>>::insert((collection.id, user), true);458		} else {459			<Allowlist<T>>::remove((collection.id, user));460		}461462		Ok(())463	}464465	pub fn toggle_admin(466		collection: &CollectionHandle<T>,467		sender: &T::CrossAccountId,468		user: &T::CrossAccountId,469		admin: bool,470	) -> DispatchResult {471		collection.check_is_owner_or_admin(&sender)?;472473		let was_admin = <IsAdmin<T>>::get((collection.id, user));474		if was_admin == admin {475			return Ok(());476		}477		let amount = <AdminAmount<T>>::get(collection.id);478479		if admin {480			let amount = amount481				.checked_add(1)482				.ok_or(<Error<T>>::CollectionAdminAmountExceeded)?;483			ensure!(484				amount <= Self::collection_admins_limit(),485				<Error<T>>::CollectionAdminAmountExceeded,486			);487488			// =========489490			<AdminAmount<T>>::insert(collection.id, amount);491			<IsAdmin<T>>::insert((collection.id, user), true);492		} else {493			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));494			<IsAdmin<T>>::remove((collection.id, user));495		}496497		Ok(())498	}499}500501#[macro_export]502macro_rules! unsupported {503	() => {504		Err(<Error<T>>::UnsupportedOperation.into())505	};506}507508/// Worst cases509pub trait CommonWeightInfo {510	fn create_item() -> Weight;511	fn create_multiple_items(amount: u32) -> Weight;512	fn burn_item() -> Weight;513	fn transfer() -> Weight;514	fn approve() -> Weight;515	fn transfer_from() -> Weight;516	fn burn_from() -> Weight;517	fn set_variable_metadata(bytes: u32) -> Weight;518}519520pub trait CommonCollectionOperations<T: Config> {521	fn create_item(522		&self,523		sender: T::CrossAccountId,524		to: T::CrossAccountId,525		data: CreateItemData,526	) -> DispatchResultWithPostInfo;527	fn create_multiple_items(528		&self,529		sender: T::CrossAccountId,530		to: T::CrossAccountId,531		data: Vec<CreateItemData>,532	) -> DispatchResultWithPostInfo;533	fn burn_item(534		&self,535		sender: T::CrossAccountId,536		token: TokenId,537		amount: u128,538	) -> DispatchResultWithPostInfo;539540	fn transfer(541		&self,542		sender: T::CrossAccountId,543		to: T::CrossAccountId,544		token: TokenId,545		amount: u128,546	) -> DispatchResultWithPostInfo;547	fn approve(548		&self,549		sender: T::CrossAccountId,550		spender: T::CrossAccountId,551		token: TokenId,552		amount: u128,553	) -> DispatchResultWithPostInfo;554	fn transfer_from(555		&self,556		sender: T::CrossAccountId,557		from: T::CrossAccountId,558		to: T::CrossAccountId,559		token: TokenId,560		amount: u128,561	) -> DispatchResultWithPostInfo;562	fn burn_from(563		&self,564		sender: T::CrossAccountId,565		from: T::CrossAccountId,566		token: TokenId,567		amount: u128,568	) -> DispatchResultWithPostInfo;569570	fn set_variable_metadata(571		&self,572		sender: T::CrossAccountId,573		token: TokenId,574		data: Vec<u8>,575	) -> DispatchResultWithPostInfo;576577	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;578	fn token_exists(&self, token: TokenId) -> bool;579	fn last_token_id(&self) -> TokenId;580581	fn token_owner(&self, token: TokenId) -> T::CrossAccountId;582	fn const_metadata(&self, token: TokenId) -> Vec<u8>;583	fn variable_metadata(&self, token: TokenId) -> Vec<u8>;584585	/// How many tokens collection contains (Applicable to nonfungible/refungible)586	fn collection_tokens(&self) -> u32;587	/// Amount of different tokens account has (Applicable to nonfungible/refungible)588	fn account_balance(&self, account: T::CrossAccountId) -> u32;589	/// Amount of specific token account have (Applicable to fungible/refungible)590	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;591	fn allowance(592		&self,593		sender: T::CrossAccountId,594		spender: T::CrossAccountId,595		token: TokenId,596	) -> u128;597}598599// Flexible enough for implementing CommonCollectionOperations600pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {601	let post_info = PostDispatchInfo {602		actual_weight: Some(weight),603		pays_fee: Pays::Yes,604	};605	match res {606		Ok(()) => Ok(post_info),607		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),608	}609}
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -36,8 +36,8 @@
 use sp_runtime::{sp_std::prelude::Vec};
 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, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,
+	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,
 };
@@ -156,7 +156,6 @@
 	where
 		origin: T::Origin
 	{
-		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;
 		type Error = Error<T>;
 
 		fn on_initialize(_now: T::BlockNumber) -> Weight {
@@ -406,12 +405,9 @@
 		#[transactional]
 		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			collection.check_is_owner_or_admin(&sender)?;
 
-			<IsAdmin<T>>::insert((collection_id, new_admin_id.as_sub()), true);
-			Ok(())
+			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)
 		}
 
 		/// 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.
@@ -430,12 +426,9 @@
 		#[transactional]
 		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
-
 			let collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			collection.check_is_owner_or_admin(&sender)?;
 
-			<IsAdmin<T>>::remove((collection_id, account_id.as_sub()));
-			Ok(())
+			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)
 		}
 
 		/// # Permissions
modifiedprimitives/nft/src/lib.rsdiffbeforeafterboth
--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -41,7 +41,7 @@
 } else {
 	10
 };
-pub const COLLECTION_ADMINS_LIMIT: u64 = 5;
+pub const COLLECTION_ADMINS_LIMIT: u32 = 5;
 pub const COLLECTION_TOKEN_LIMIT: u32 = u32::MAX;
 pub const ACCOUNT_TOKEN_OWNERSHIP_LIMIT: u32 = if cfg!(not(feature = "limit-testing")) {
 	1000000