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

difftreelog

refactor(nonfungible) merge TokenData & Owner

Yaroslav Bolyukin2021-10-22parent: #3528861.patch.diff
in: master

3 files changed

modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -190,13 +190,19 @@
 	}
 
 	fn token_owner(&self, token: TokenId) -> T::CrossAccountId {
-		<Owner<T>>::get((self.id, token))
+		<TokenData<T>>::get((self.id, token))
+			.map(|t| t.owner)
+			.unwrap_or_default()
 	}
 	fn const_metadata(&self, token: TokenId) -> Vec<u8> {
-		<TokenData<T>>::get((self.id, token, DataKind::Constant))
+		<TokenData<T>>::get((self.id, token))
+			.map(|t| t.const_data.clone())
+			.unwrap_or_default()
 	}
 	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {
-		<TokenData<T>>::get((self.id, token, DataKind::Variable))
+		<TokenData<T>>::get((self.id, token))
+			.map(|t| t.variable_data.clone())
+			.unwrap_or_default()
 	}
 
 	fn collection_tokens(&self) -> u32 {
@@ -208,7 +214,10 @@
 	}
 
 	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {
-		if <Owner<T>>::get((self.id, token)) == account {
+		if <TokenData<T>>::get((self.id, token))
+			.map(|a| a.owner == account)
+			.unwrap_or(false)
+		{
 			1
 		} else {
 			0
@@ -221,7 +230,10 @@
 		spender: T::CrossAccountId,
 		token: TokenId,
 	) -> u128 {
-		if <Owner<T>>::get((self.id, token)) != sender {
+		if <TokenData<T>>::get((self.id, token))
+			.map(|a| a.owner == sender)
+			.unwrap_or(false)
+		{
 			0
 		} else if <Allowance<T>>::get((self.id, token)) == Some(spender) {
 			1
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -13,8 +13,7 @@
 use pallet_common::erc::PrecompileOutput;
 
 use crate::{
-	AccountBalance, Config, CreateItemData, DataKind, NonfungibleHandle, Owner, Pallet, TokenData,
-	TokensMinted,
+	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,
 };
 
 #[derive(ToLog)]
@@ -65,11 +64,11 @@
 	#[solidity(rename_selector = "tokenURI")]
 	fn token_uri(&self, token_id: uint256) -> Result<string> {
 		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
-		Ok(string::from_utf8_lossy(&<TokenData<T>>::get((
-			self.id,
-			token_id,
-			DataKind::Constant,
-		)))
+		Ok(string::from_utf8_lossy(
+			&<TokenData<T>>::get((self.id, token_id))
+				.ok_or("token not found")?
+				.const_data,
+		)
 		.into())
 	}
 }
@@ -99,7 +98,10 @@
 	}
 	fn owner_of(&self, token_id: uint256) -> Result<address> {
 		let token: TokenId = token_id.try_into()?;
-		Ok(*<Owner<T>>::get((self.id, token)).as_eth())
+		Ok(*<TokenData<T>>::get((self.id, token))
+			.ok_or("token not found")?
+			.owner
+			.as_eth())
 	}
 	fn safe_transfer_from_with_data(
 		&mut self,
@@ -302,7 +304,9 @@
 	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {
 		let token: TokenId = token_id.try_into()?;
 
-		Ok(<TokenData<T>>::get((self.id, token, DataKind::Variable)))
+		Ok(<TokenData<T>>::get((self.id, token))
+			.ok_or("token not found")?
+			.variable_data)
 	}
 
 	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/src/lib.rs
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;1617pub use pallet::*;18pub mod benchmarking;19pub mod common;20pub mod erc;21pub mod weights;2223pub struct CreateItemData<T: Config> {24	pub const_data: BoundedVec<u8, CustomDataLimit>,25	pub variable_data: BoundedVec<u8, CustomDataLimit>,26	pub owner: T::CrossAccountId,27}28pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;2930#[frame_support::pallet]31pub mod pallet {32	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};33	use sp_std::vec::Vec;34	use nft_data_structs::{CollectionId, TokenId};35	use super::weights::WeightInfo;3637	#[pallet::error]38	pub enum Error<T> {39		/// Not Nonfungible item data used to mint in Nonfungible collection.40		NotNonfungibleDataUsedToMintFungibleCollectionToken,41		/// Used amount > 1 with NFT42		NonfungibleItemsHaveNoAmount,43	}4445	#[pallet::config]46	pub trait Config: frame_system::Config + pallet_common::Config {47		type WeightInfo: WeightInfo;48	}4950	#[pallet::pallet]51	#[pallet::generate_store(pub(super) trait Store)]52	pub struct Pallet<T>(_);5354	#[pallet::storage]55	pub(super) type TokensMinted<T: Config> =56		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;57	#[pallet::storage]58	pub(super) type TokensBurnt<T: Config> =59		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;6061	#[derive(Encode, Decode)]62	pub enum DataKind {63		Constant,64		Variable,65	}6667	#[pallet::storage]68	pub(super) type TokenData<T: Config> = StorageNMap<69		Key = (70			Key<Twox64Concat, CollectionId>,71			Key<Twox64Concat, TokenId>,72			Key<Identity, DataKind>,73		),74		Value = Vec<u8>,75		QueryKind = ValueQuery,76	>;7778	#[pallet::storage]79	pub(super) type Owner<T: Config> = StorageNMap<80		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),81		Value = T::CrossAccountId,82		QueryKind = ValueQuery,83	>;84	/// Used to enumerate tokens owned by account85	#[pallet::storage]86	pub(super) type Owned<T: Config> = StorageNMap<87		Key = (88			Key<Twox64Concat, CollectionId>,89			Key<Blake2_128Concat, T::AccountId>,90			Key<Twox64Concat, TokenId>,91		),92		Value = bool,93		QueryKind = ValueQuery,94	>;9596	#[pallet::storage]97	pub(super) type AccountBalance<T: Config> = StorageNMap<98		Key = (99			Key<Twox64Concat, CollectionId>,100			Key<Blake2_128Concat, T::AccountId>,101		),102		Value = u32,103		QueryKind = ValueQuery,104	>;105106	#[pallet::storage]107	pub(super) type Allowance<T: Config> = StorageNMap<108		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),109		Value = T::CrossAccountId,110		QueryKind = OptionQuery,111	>;112}113114pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);115impl<T: Config> NonfungibleHandle<T> {116	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {117		Self(inner)118	}119	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {120		self.0121	}122}123impl<T: Config> Deref for NonfungibleHandle<T> {124	type Target = pallet_common::CollectionHandle<T>;125126	fn deref(&self) -> &Self::Target {127		&self.0128	}129}130131impl<T: Config> Pallet<T> {132	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {133		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)134	}135	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {136		<Owner<T>>::contains_key((collection.id, token))137	}138	pub fn ensure_owner(139		collection: &NonfungibleHandle<T>,140		token: TokenId,141		sender: &T::CrossAccountId,142	) -> DispatchResult {143		ensure!(144			&<Owner<T>>::get((collection.id, token)) == sender,145			<CommonError<T>>::NoPermission146		);147		Ok(())148	}149	pub fn item_owner(150		collection: &NonfungibleHandle<T>,151		token: TokenId,152	) -> Result<T::CrossAccountId, DispatchError> {153		let owner = <Owner<T>>::get((collection.id, token));154		ensure!(155			owner != T::CrossAccountId::default(),156			<CommonError<T>>::TokenNotFound157		);158		Ok(owner)159	}160}161162// unchecked calls skips any permission checks163impl<T: Config> Pallet<T> {164	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {165		PalletCommon::init_collection(data)166	}167	pub fn destroy_collection(168		collection: NonfungibleHandle<T>,169		sender: &T::CrossAccountId,170	) -> DispatchResult {171		let id = collection.id;172173		// =========174175		PalletCommon::destroy_collection(collection.0, sender)?;176177		<Owner<T>>::remove_prefix((id,), None);178		<Owned<T>>::remove_prefix((id,), None);179		<TokensMinted<T>>::remove(id);180		<TokensBurnt<T>>::remove(id);181		<TokenData<T>>::remove_prefix((id,), None);182		<Allowance<T>>::remove_prefix((id,), None);183		<AccountBalance<T>>::remove_prefix((id,), None);184		Ok(())185	}186187	pub fn burn(188		collection: &NonfungibleHandle<T>,189		sender: &T::CrossAccountId,190		token: TokenId,191	) -> DispatchResult {192		let token_owner = <Pallet<T>>::item_owner(collection, token)?;193		ensure!(194			&token_owner == sender195				|| (collection.limits.owner_can_transfer196					&& collection.is_owner_or_admin(sender)?),197			<CommonError<T>>::NoPermission198		);199200		if collection.access == AccessMode::WhiteList {201			collection.check_allowlist(sender)?;202		}203204		let burnt = <TokensBurnt<T>>::get(collection.id)205			.checked_add(1)206			.ok_or(ArithmeticError::Overflow)?;207208		// =========209210		<Owner<T>>::remove((collection.id, token));211		<Owned<T>>::remove((collection.id, token_owner.as_sub(), token));212		<TokensBurnt<T>>::insert(collection.id, burnt);213		<TokenData<T>>::remove_prefix((collection.id, token), None);214		<Allowance<T>>::remove((collection.id, token));215216		collection.log_infallible(ERC721Events::Transfer {217			from: *token_owner.as_eth(),218			to: H160::default(),219			token_id: token.into(),220		});221		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(222			collection.id,223			token,224			token_owner,225			1,226		));227		return Ok(());228	}229230	pub fn transfer(231		collection: &NonfungibleHandle<T>,232		from: &T::CrossAccountId,233		to: &T::CrossAccountId,234		token: TokenId,235	) -> DispatchResult {236		ensure!(237			collection.transfers_enabled,238			<CommonError<T>>::TransferNotAllowed239		);240241		let token_owner = <Pallet<T>>::item_owner(collection, token)?;242		ensure!(243			&token_owner == from244				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),245			<CommonError<T>>::NoPermission246		);247248		if collection.access == AccessMode::WhiteList {249			collection.check_allowlist(from)?;250			collection.check_allowlist(to)?;251		}252		<PalletCommon<T>>::ensure_correct_receiver(to)?;253254		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))255			.checked_sub(1)256			.ok_or(<CommonError<T>>::TokenValueTooLow)?;257		let balance_to = if from != to {258			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))259				.checked_add(1)260				.ok_or(ArithmeticError::Overflow)?;261262			ensure!(263				balance_to < collection.limits.account_token_ownership_limit(),264				<CommonError<T>>::AccountTokenLimitExceeded,265			);266267			Some(balance_to)268		} else {269			None270		};271272		collection.consume_sstores(4)?;273		collection.consume_log(3, 0)?;274275		// =========276277		if let Some(balance_to) = balance_to {278			// from != to279			if balance_from == 0 {280				<AccountBalance<T>>::remove((collection.id, from.as_sub()));281			} else {282				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);283			}284			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);285			<Owned<T>>::remove((collection.id, from.as_sub(), token));286			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);287		}288		Self::set_allowance_unchecked(collection, from, token, None);289		<Owner<T>>::insert((collection.id, token), &to);290291		collection.log_infallible(ERC721Events::Transfer {292			from: *from.as_eth(),293			to: *to.as_eth(),294			token_id: token.into(),295		});296		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(297			collection.id,298			token,299			from.clone(),300			to.clone(),301			1,302		));303		Ok(())304	}305306	pub fn create_multiple_items(307		collection: &NonfungibleHandle<T>,308		sender: &T::CrossAccountId,309		data: Vec<CreateItemData<T>>,310	) -> DispatchResult {311		let unrestricted_minting = collection.is_owner_or_admin(sender)?;312		if !unrestricted_minting {313			ensure!(314				collection.mint_mode,315				<CommonError<T>>::PublicMintingNotAllowed316			);317			collection.check_allowlist(sender)?;318319			for item in data.iter() {320				collection.check_allowlist(&item.owner)?;321			}322		}323324		for data in data.iter() {325			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;326			if !data.const_data.is_empty() {327				collection.consume_sstore()?;328			}329			if !data.variable_data.is_empty() {330				collection.consume_sstore()?;331			}332			collection.consume_sstore()?;333			collection.consume_log(3, 0)?;334		}335336		let first_token = <TokensMinted<T>>::get(collection.id);337		let tokens_minted = first_token338			.checked_add(data.len() as u32)339			.ok_or(ArithmeticError::Overflow)?;340		ensure!(341			tokens_minted < collection.limits.token_limit,342			<CommonError<T>>::CollectionTokenLimitExceeded343		);344		collection.consume_sstore()?;345346		let mut balances = BTreeMap::new();347		for data in &data {348			let balance = balances349				.entry(data.owner.as_sub())350				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));351			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;352353			ensure!(354				*balance <= collection.limits.account_token_ownership_limit(),355				<CommonError<T>>::AccountTokenLimitExceeded,356			);357		}358		collection.consume_sstores(balances.len())?;359360		// =========361362		<TokensMinted<T>>::insert(collection.id, tokens_minted);363		for (account, balance) in balances {364			<AccountBalance<T>>::insert((collection.id, account), balance);365		}366		for (i, data) in data.into_iter().enumerate() {367			let token = first_token + i as u32;368369			if !data.const_data.is_empty() {370				<TokenData<T>>::insert((collection.id, token, DataKind::Constant), data.const_data);371			}372			if !data.variable_data.is_empty() {373				<TokenData<T>>::insert(374					(collection.id, token, DataKind::Variable),375					data.variable_data,376				);377			}378			<Owner<T>>::insert((collection.id, token), &data.owner);379			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);380381			collection.log_infallible(ERC721Events::Transfer {382				from: H160::default(),383				to: *data.owner.as_eth(),384				token_id: token.into(),385			});386		}387		Ok(())388	}389390	pub fn set_allowance_unchecked(391		collection: &NonfungibleHandle<T>,392		sender: &T::CrossAccountId,393		token: TokenId,394		spender: Option<&T::CrossAccountId>,395	) {396		if let Some(spender) = spender {397			let old_spender = <Allowance<T>>::get((collection.id, token));398			<Allowance<T>>::insert((collection.id, token), spender);399			// In ERC721 there is only one possible approved user of token, so we set400			// approved user to spender401			collection.log_infallible(ERC721Events::Approval {402				owner: *sender.as_eth(),403				approved: *spender.as_eth(),404				token_id: token.into(),405			});406			// In Unique chain, any token can have any amount of approved users, so we need to407			// set allowance of old owner to 0, and allowance of new owner to 1408			if old_spender.as_ref() != Some(spender) {409				if let Some(old_owner) = old_spender {410					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(411						collection.id,412						token,413						sender.clone(),414						old_owner.clone(),415						0,416					));417				}418				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(419					collection.id,420					token,421					sender.clone(),422					spender.clone(),423					1,424				));425			}426		} else {427			let old_spender = <Allowance<T>>::take((collection.id, token));428			// In ERC721 there is only one possible approved user of token, so we set429			// approved user to zero address430			collection.log_infallible(ERC721Events::Approval {431				owner: *sender.as_eth(),432				approved: H160::default(),433				token_id: token.into(),434			});435			// In Unique chain, any token can have any amount of approved users, so we need to436			// set allowance of old owner to 0437			if let Some(old_spender) = old_spender {438				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(439					collection.id,440					token,441					sender.clone(),442					old_spender.clone(),443					0,444				));445			}446		}447	}448449	pub fn set_allowance(450		collection: &NonfungibleHandle<T>,451		sender: &T::CrossAccountId,452		token: TokenId,453		spender: Option<&T::CrossAccountId>,454	) -> DispatchResult {455		if collection.access == AccessMode::WhiteList {456			collection.check_allowlist(&sender)?;457			if let Some(spender) = spender {458				collection.check_allowlist(&spender)?;459			}460		}461462		if let Some(spender) = spender {463			<PalletCommon<T>>::ensure_correct_receiver(spender)?;464		}465		let token_owner = Self::item_owner(collection, token)?;466		if &token_owner != sender {467			ensure!(468				collection.ignores_owned_amount(sender)?,469				<CommonError<T>>::CantApproveMoreThanOwned470			);471		}472473		// =========474475		Self::set_allowance_unchecked(collection, sender, token, spender);476		Ok(())477	}478479	pub fn transfer_from(480		collection: &NonfungibleHandle<T>,481		spender: &T::CrossAccountId,482		from: &T::CrossAccountId,483		to: &T::CrossAccountId,484		token: TokenId,485	) -> DispatchResult {486		if spender == from {487			return Self::transfer(collection, from, to, token);488		}489		if collection.access == AccessMode::WhiteList {490			// `from`, `to` checked in [`transfer`]491			collection.check_allowlist(spender)?;492		}493494		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {495			ensure!(496				collection.ignores_allowance(spender)?,497				<CommonError<T>>::TokenValueNotEnough498			);499		}500501		// =========502503		Self::transfer(collection, &from, to, token)?;504		// Allowance is reset in [`transfer`]505		Ok(())506	}507508	pub fn set_variable_metadata(509		collection: &NonfungibleHandle<T>,510		sender: &T::CrossAccountId,511		token: TokenId,512		data: Vec<u8>,513	) -> DispatchResult {514		ensure!(515			data.len() as u32 <= CUSTOM_DATA_LIMIT,516			<CommonError<T>>::TokenVariableDataLimitExceeded517		);518		let item_owner = Self::item_owner(collection, token)?;519		collection.check_can_update_meta(sender, &item_owner)?;520521		collection.consume_sstore()?;522523		// =========524525		<TokenData<T>>::insert((collection.id, token, DataKind::Variable), data);526		Ok(())527	}528529	/// Delegated to `create_multiple_items`530	pub fn create_item(531		collection: &NonfungibleHandle<T>,532		sender: &T::CrossAccountId,533		data: CreateItemData<T>,534	) -> DispatchResult {535		Self::create_multiple_items(collection, sender, vec![data])536	}537}
after · pallets/nonfungible/src/lib.rs
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}