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}
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