git.delta.rocks / unique-network / refs/commits / 54a17a4acbdb

difftreelog

source

pallets/nonfungible/src/lib.rs14.2 KiBsourcehistory
1#![cfg_attr(not(feature = "std"), no_std)]23use erc::ERC721Events;4use frame_support::{BoundedVec, ensure};5use nft_data_structs::{6	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,7};8use pallet_common::{9	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,10};11use sp_core::H160;12use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};13use sp_std::{vec::Vec, vec};14use core::ops::Deref;15use sp_std::collections::btree_map::BTreeMap;16use codec::{Encode, Decode};1718pub use pallet::*;19pub mod benchmarking;20pub mod common;21pub mod erc;22pub mod weights;2324pub struct CreateItemData<T: Config> {25	pub const_data: BoundedVec<u8, CustomDataLimit>,26	pub variable_data: BoundedVec<u8, CustomDataLimit>,27	pub owner: T::CrossAccountId,28}29pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;3031#[derive(Encode, Decode)]32pub struct ItemData<T: Config> {33	pub const_data: Vec<u8>,34	pub variable_data: Vec<u8>,35	pub owner: T::CrossAccountId,36}3738#[frame_support::pallet]39pub mod pallet {40	use super::*;41	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};42	use nft_data_structs::{CollectionId, TokenId};43	use super::weights::WeightInfo;4445	#[pallet::error]46	pub enum Error<T> {47		/// Not Nonfungible item data used to mint in Nonfungible collection.48		NotNonfungibleDataUsedToMintFungibleCollectionToken,49		/// Used amount > 1 with NFT50		NonfungibleItemsHaveNoAmount,51	}5253	#[pallet::config]54	pub trait Config: frame_system::Config + pallet_common::Config {55		type WeightInfo: WeightInfo;56	}5758	#[pallet::pallet]59	#[pallet::generate_store(pub(super) trait Store)]60	pub struct Pallet<T>(_);6162	#[pallet::storage]63	pub(super) type TokensMinted<T: Config> =64		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;65	#[pallet::storage]66	pub(super) type TokensBurnt<T: Config> =67		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;6869	#[pallet::storage]70	pub(super) type TokenData<T: Config> = StorageNMap<71		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),72		Value = ItemData<T>,73		QueryKind = OptionQuery,74	>;7576	/// Used to enumerate tokens owned by account77	#[pallet::storage]78	pub(super) type Owned<T: Config> = StorageNMap<79		Key = (80			Key<Twox64Concat, CollectionId>,81			Key<Blake2_128Concat, T::AccountId>,82			Key<Twox64Concat, TokenId>,83		),84		Value = bool,85		QueryKind = ValueQuery,86	>;8788	#[pallet::storage]89	pub(super) type AccountBalance<T: Config> = StorageNMap<90		Key = (91			Key<Twox64Concat, CollectionId>,92			Key<Blake2_128Concat, T::AccountId>,93		),94		Value = u32,95		QueryKind = ValueQuery,96	>;9798	#[pallet::storage]99	pub(super) type Allowance<T: Config> = StorageNMap<100		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),101		Value = T::CrossAccountId,102		QueryKind = OptionQuery,103	>;104}105106pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);107impl<T: Config> NonfungibleHandle<T> {108	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {109		Self(inner)110	}111	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {112		self.0113	}114}115impl<T: Config> Deref for NonfungibleHandle<T> {116	type Target = pallet_common::CollectionHandle<T>;117118	fn deref(&self) -> &Self::Target {119		&self.0120	}121}122123impl<T: Config> Pallet<T> {124	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {125		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)126	}127	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {128		<TokenData<T>>::contains_key((collection.id, token))129	}130}131132// unchecked calls skips any permission checks133impl<T: Config> Pallet<T> {134	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {135		PalletCommon::init_collection(data)136	}137	pub fn destroy_collection(138		collection: NonfungibleHandle<T>,139		sender: &T::CrossAccountId,140	) -> DispatchResult {141		let id = collection.id;142143		// =========144145		PalletCommon::destroy_collection(collection.0, sender)?;146147		<TokenData<T>>::remove_prefix((id,), None);148		<Owned<T>>::remove_prefix((id,), None);149		<TokensMinted<T>>::remove(id);150		<TokensBurnt<T>>::remove(id);151		<Allowance<T>>::remove_prefix((id,), None);152		<AccountBalance<T>>::remove_prefix((id,), None);153		Ok(())154	}155156	pub fn burn(157		collection: &NonfungibleHandle<T>,158		sender: &T::CrossAccountId,159		token: TokenId,160	) -> DispatchResult {161		let token_data = <TokenData<T>>::get((collection.id, token))162			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;163		ensure!(164			&token_data.owner == sender165				|| (collection.limits.owner_can_transfer166					&& collection.is_owner_or_admin(sender)?),167			<CommonError<T>>::NoPermission168		);169170		if collection.access == AccessMode::WhiteList {171			collection.check_allowlist(sender)?;172		}173174		let burnt = <TokensBurnt<T>>::get(collection.id)175			.checked_add(1)176			.ok_or(ArithmeticError::Overflow)?;177178		// =========179180		<Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));181		<TokensBurnt<T>>::insert(collection.id, burnt);182		<TokenData<T>>::remove((collection.id, token));183		let old_spender = <Allowance<T>>::take((collection.id, token));184185		if let Some(old_spender) = old_spender {186			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(187				collection.id,188				token,189				sender.clone(),190				old_spender.clone(),191				0,192			));193		}194195		collection.log_infallible(ERC721Events::Transfer {196			from: *token_data.owner.as_eth(),197			to: H160::default(),198			token_id: token.into(),199		});200		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(201			collection.id,202			token,203			token_data.owner,204			1,205		));206		return Ok(());207	}208209	pub fn transfer(210		collection: &NonfungibleHandle<T>,211		from: &T::CrossAccountId,212		to: &T::CrossAccountId,213		token: TokenId,214	) -> DispatchResult {215		ensure!(216			collection.transfers_enabled,217			<CommonError<T>>::TransferNotAllowed218		);219220		let token_data = <TokenData<T>>::get((collection.id, token))221			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;222		ensure!(223			&token_data.owner == from224				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),225			<CommonError<T>>::NoPermission226		);227228		if collection.access == AccessMode::WhiteList {229			collection.check_allowlist(from)?;230			collection.check_allowlist(to)?;231		}232		<PalletCommon<T>>::ensure_correct_receiver(to)?;233234		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))235			.checked_sub(1)236			.ok_or(<CommonError<T>>::TokenValueTooLow)?;237		let balance_to = if from != to {238			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))239				.checked_add(1)240				.ok_or(ArithmeticError::Overflow)?;241242			ensure!(243				balance_to < collection.limits.account_token_ownership_limit(),244				<CommonError<T>>::AccountTokenLimitExceeded,245			);246247			Some(balance_to)248		} else {249			None250		};251252		collection.consume_sstores(4)?;253		collection.consume_log(3, 0)?;254255		// =========256257		<TokenData<T>>::insert(258			(collection.id, token),259			ItemData {260				owner: to.clone(),261				..token_data262			},263		);264265		if let Some(balance_to) = balance_to {266			// from != to267			if balance_from == 0 {268				<AccountBalance<T>>::remove((collection.id, from.as_sub()));269			} else {270				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);271			}272			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);273			<Owned<T>>::remove((collection.id, from.as_sub(), token));274			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);275		}276		Self::set_allowance_unchecked(collection, from, token, None);277278		collection.log_infallible(ERC721Events::Transfer {279			from: *from.as_eth(),280			to: *to.as_eth(),281			token_id: token.into(),282		});283		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(284			collection.id,285			token,286			from.clone(),287			to.clone(),288			1,289		));290		Ok(())291	}292293	pub fn create_multiple_items(294		collection: &NonfungibleHandle<T>,295		sender: &T::CrossAccountId,296		data: Vec<CreateItemData<T>>,297	) -> DispatchResult {298		let unrestricted_minting = collection.is_owner_or_admin(sender)?;299		if !unrestricted_minting {300			ensure!(301				collection.mint_mode,302				<CommonError<T>>::PublicMintingNotAllowed303			);304			collection.check_allowlist(sender)?;305306			for item in data.iter() {307				collection.check_allowlist(&item.owner)?;308			}309		}310311		for data in data.iter() {312			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;313			if !data.const_data.is_empty() {314				collection.consume_sstore()?;315			}316			if !data.variable_data.is_empty() {317				collection.consume_sstore()?;318			}319			collection.consume_sstore()?;320			collection.consume_log(3, 0)?;321		}322323		let first_token = <TokensMinted<T>>::get(collection.id);324		let tokens_minted = first_token325			.checked_add(data.len() as u32)326			.ok_or(ArithmeticError::Overflow)?;327		ensure!(328			tokens_minted < collection.limits.token_limit,329			<CommonError<T>>::CollectionTokenLimitExceeded330		);331		collection.consume_sstore()?;332333		let mut balances = BTreeMap::new();334		for data in &data {335			let balance = balances336				.entry(data.owner.as_sub())337				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));338			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;339340			ensure!(341				*balance <= collection.limits.account_token_ownership_limit(),342				<CommonError<T>>::AccountTokenLimitExceeded,343			);344		}345		collection.consume_sstores(balances.len())?;346347		// =========348349		<TokensMinted<T>>::insert(collection.id, tokens_minted);350		for (account, balance) in balances {351			<AccountBalance<T>>::insert((collection.id, account), balance);352		}353		for (i, data) in data.into_iter().enumerate() {354			let token = first_token + i as u32 + 1;355356			<TokenData<T>>::insert(357				(collection.id, token),358				ItemData {359					const_data: data.const_data.into(),360					variable_data: data.variable_data.into(),361					owner: data.owner.clone(),362				},363			);364			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);365366			collection.log_infallible(ERC721Events::Transfer {367				from: H160::default(),368				to: *data.owner.as_eth(),369				token_id: token.into(),370			});371			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(372				collection.id,373				TokenId(token),374				data.owner.clone(),375				1,376			));377		}378		Ok(())379	}380381	pub fn set_allowance_unchecked(382		collection: &NonfungibleHandle<T>,383		sender: &T::CrossAccountId,384		token: TokenId,385		spender: Option<&T::CrossAccountId>,386	) {387		if let Some(spender) = spender {388			let old_spender = <Allowance<T>>::get((collection.id, token));389			<Allowance<T>>::insert((collection.id, token), spender);390			// In ERC721 there is only one possible approved user of token, so we set391			// approved user to spender392			collection.log_infallible(ERC721Events::Approval {393				owner: *sender.as_eth(),394				approved: *spender.as_eth(),395				token_id: token.into(),396			});397			// In Unique chain, any token can have any amount of approved users, so we need to398			// set allowance of old owner to 0, and allowance of new owner to 1399			if old_spender.as_ref() != Some(spender) {400				if let Some(old_owner) = old_spender {401					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(402						collection.id,403						token,404						sender.clone(),405						old_owner.clone(),406						0,407					));408				}409				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(410					collection.id,411					token,412					sender.clone(),413					spender.clone(),414					1,415				));416			}417		} else {418			let old_spender = <Allowance<T>>::take((collection.id, token));419			// In ERC721 there is only one possible approved user of token, so we set420			// approved user to zero address421			collection.log_infallible(ERC721Events::Approval {422				owner: *sender.as_eth(),423				approved: H160::default(),424				token_id: token.into(),425			});426			// In Unique chain, any token can have any amount of approved users, so we need to427			// set allowance of old owner to 0428			if let Some(old_spender) = old_spender {429				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(430					collection.id,431					token,432					sender.clone(),433					old_spender.clone(),434					0,435				));436			}437		}438	}439440	pub fn set_allowance(441		collection: &NonfungibleHandle<T>,442		sender: &T::CrossAccountId,443		token: TokenId,444		spender: Option<&T::CrossAccountId>,445	) -> DispatchResult {446		if collection.access == AccessMode::WhiteList {447			collection.check_allowlist(&sender)?;448			if let Some(spender) = spender {449				collection.check_allowlist(&spender)?;450			}451		}452453		if let Some(spender) = spender {454			<PalletCommon<T>>::ensure_correct_receiver(spender)?;455		}456		let token_data =457			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;458		if &token_data.owner != sender {459			ensure!(460				collection.ignores_owned_amount(sender)?,461				<CommonError<T>>::CantApproveMoreThanOwned462			);463		}464465		// =========466467		Self::set_allowance_unchecked(collection, sender, token, spender);468		Ok(())469	}470471	pub fn transfer_from(472		collection: &NonfungibleHandle<T>,473		spender: &T::CrossAccountId,474		from: &T::CrossAccountId,475		to: &T::CrossAccountId,476		token: TokenId,477	) -> DispatchResult {478		if spender == from {479			return Self::transfer(collection, from, to, token);480		}481		if collection.access == AccessMode::WhiteList {482			// `from`, `to` checked in [`transfer`]483			collection.check_allowlist(spender)?;484		}485486		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {487			ensure!(488				collection.ignores_allowance(spender)?,489				<CommonError<T>>::TokenValueNotEnough490			);491		}492493		// =========494495		Self::transfer(collection, &from, to, token)?;496		// Allowance is reset in [`transfer`]497		Ok(())498	}499500	pub fn set_variable_metadata(501		collection: &NonfungibleHandle<T>,502		sender: &T::CrossAccountId,503		token: TokenId,504		data: Vec<u8>,505	) -> DispatchResult {506		ensure!(507			data.len() as u32 <= CUSTOM_DATA_LIMIT,508			<CommonError<T>>::TokenVariableDataLimitExceeded509		);510		let token_data =511			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;512		collection.check_can_update_meta(sender, &token_data.owner)?;513514		collection.consume_sstore()?;515516		// =========517518		<TokenData<T>>::insert(519			(collection.id, token),520			ItemData {521				variable_data: data,522				..token_data523			},524		);525		Ok(())526	}527528	/// Delegated to `create_multiple_items`529	pub fn create_item(530		collection: &NonfungibleHandle<T>,531		sender: &T::CrossAccountId,532		data: CreateItemData<T>,533	) -> DispatchResult {534		Self::create_multiple_items(collection, sender, vec![data])535	}536}