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}