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

difftreelog

source

pallets/common/src/lib.rs14.8 KiBsourcehistory
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.as_sub())))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.as_sub())),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::{pallet_prelude::*};143	use frame_support::{Blake2_128Concat, 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::event]170	#[pallet::generate_deposit(pub fn deposit_event)]171	pub enum Event<T: Config> {172		/// New collection was created173		///174		/// # Arguments175		///176		/// * collection_id: Globally unique identifier of newly created collection.177		///178		/// * mode: [CollectionMode] converted into u8.179		///180		/// * account_id: Collection owner.181		CollectionCreated(CollectionId, u8, T::AccountId),182183		/// New item was created.184		///185		/// # Arguments186		///187		/// * collection_id: Id of the collection where item was created.188		///189		/// * item_id: Id of an item. Unique within the collection.190		///191		/// * recipient: Owner of newly created item192		///193		/// * amount: Always 1 for NFT194		ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),195196		/// Collection item was burned.197		///198		/// # Arguments199		///200		/// * collection_id.201		///202		/// * item_id: Identifier of burned NFT.203		///204		/// * owner: which user has destroyed its tokens205		///206		/// * amount: Always 1 for NFT207		ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),208209		/// Item was transferred210		///211		/// * collection_id: Id of collection to which item is belong212		///213		/// * item_id: Id of an item214		///215		/// * sender: Original owner of item216		///217		/// * recipient: New owner of item218		///219		/// * amount: Always 1 for NFT220		Transfer(221			CollectionId,222			TokenId,223			T::CrossAccountId,224			T::CrossAccountId,225			u128,226		),227228		/// * collection_id229		///230		/// * item_id231		///232		/// * sender233		///234		/// * spender235		///236		/// * amount237		Approved(238			CollectionId,239			TokenId,240			T::CrossAccountId,241			T::CrossAccountId,242			u128,243		),244	}245246	#[pallet::error]247	pub enum Error<T> {248		/// This collection does not exist.249		CollectionNotFound,250		/// Sender parameter and item owner must be equal.251		MustBeTokenOwner,252		/// No permission to perform action253		NoPermission,254		/// Collection is not in mint mode.255		PublicMintingNotAllowed,256		/// Address is not in white list.257		AddressNotInAllowlist,258259		/// Collection name can not be longer than 63 char.260		CollectionNameLimitExceeded,261		/// Collection description can not be longer than 255 char.262		CollectionDescriptionLimitExceeded,263		/// Token prefix can not be longer than 15 char.264		CollectionTokenPrefixLimitExceeded,265		/// Total collections bound exceeded.266		TotalCollectionsLimitExceeded,267		/// variable_data exceeded data limit.268		TokenVariableDataLimitExceeded,269270		/// Collection settings not allowing items transferring271		TransferNotAllowed,272		/// Account token limit exceeded per collection273		AccountTokenLimitExceeded,274		/// Collection token limit exceeded275		CollectionTokenLimitExceeded,276		/// Metadata flag frozen277		MetadataFlagFrozen,278279		/// Item not exists.280		TokenNotFound,281		/// Item balance not enough.282		TokenValueTooLow,283		/// Requested value more than approved.284		TokenValueNotEnough,285		/// Tried to approve more than owned286		CantApproveMoreThanOwned,287288		/// Can't transfer tokens to ethereum zero address289		AddressIsZero,290		/// Target collection doesn't supports this operation291		UnsupportedOperation,292	}293294	#[pallet::storage]295	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;296	#[pallet::storage]297	pub type DestroyedCollectionCount<T> =298		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;299300	/// Collection info301	#[pallet::storage]302	pub type CollectionById<T> = StorageMap<303		Hasher = Blake2_128Concat,304		Key = CollectionId,305		Value = Collection<T>,306		QueryKind = OptionQuery,307	>;308309	/// List of collection admins310	#[pallet::storage]311	pub type IsAdmin<T: Config> = StorageNMap<312		Key = (313			Key<Blake2_128Concat, CollectionId>,314			Key<Blake2_128Concat, T::AccountId>,315		),316		Value = bool,317		QueryKind = ValueQuery,318	>;319320	/// Allowlisted collection users321	#[pallet::storage]322	pub type Allowlist<T: Config> = StorageNMap<323		Key = (324			Key<Blake2_128Concat, CollectionId>,325			Key<Blake2_128Concat, T::AccountId>,326		),327		Value = bool,328		QueryKind = ValueQuery,329	>;330}331332impl<T: Config> Pallet<T> {333	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens334	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {335		ensure!(336			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,337			<Error<T>>::AddressIsZero338		);339		Ok(())340	}341}342343impl<T: Config> Pallet<T> {344	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {345		{346			ensure!(347				data.name.len() <= MAX_COLLECTION_NAME_LENGTH,348				Error::<T>::CollectionNameLimitExceeded349			);350			ensure!(351				data.description.len() <= MAX_COLLECTION_DESCRIPTION_LENGTH,352				Error::<T>::CollectionDescriptionLimitExceeded353			);354			ensure!(355				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH,356				Error::<T>::CollectionTokenPrefixLimitExceeded357			);358		}359360		let created_count = <CreatedCollectionCount<T>>::get()361			.0362			.checked_add(1)363			.ok_or(ArithmeticError::Overflow)?;364		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;365		let id = CollectionId(created_count);366367		// bound Total number of collections368		ensure!(369			created_count - destroyed_count < COLLECTION_NUMBER_LIMIT,370			<Error<T>>::TotalCollectionsLimitExceeded371		);372373		// =========374375		// Take a (non-refundable) deposit of collection creation376		{377			let mut imbalance =378				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();379			imbalance.subsume(380				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(381					&T::TreasuryAccountId::get(),382					T::CollectionCreationPrice::get(),383				),384			);385			<T as Config>::Currency::settle(386				&data.owner,387				imbalance,388				WithdrawReasons::TRANSFER,389				ExistenceRequirement::KeepAlive,390			)391			.map_err(|_| Error::<T>::NoPermission)?;392		}393394		<CreatedCollectionCount<T>>::put(created_count);395		<Pallet<T>>::deposit_event(Event::CollectionCreated(396			id,397			data.mode.id(),398			data.owner.clone(),399		));400		<CollectionById<T>>::insert(id, data);401		Ok(id)402	}403404	pub fn destroy_collection(405		collection: CollectionHandle<T>,406		sender: &T::CrossAccountId,407	) -> DispatchResult {408		if !collection.limits.owner_can_destroy {409			fail!(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}