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
before · pallets/nonfungible/src/common.rs
1use core::marker::PhantomData;23use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};4use nft_data_structs::TokenId;5use pallet_common::{6	CommonCollectionOperations, CommonWeightInfo, account::CrossAccountId, with_weight,7};8use sp_runtime::DispatchError;9use sp_std::vec::Vec;1011use crate::{12	AccountBalance, Allowance, Config, CreateItemData, DataKind, Error, NonfungibleHandle, Owned,13	Owner, Pallet, SelfWeightOf, TokenData, weights::WeightInfo,14};1516pub struct CommonWeights<T: Config>(PhantomData<T>);17impl<T: Config> CommonWeightInfo for CommonWeights<T> {18	fn create_item() -> Weight {19		<SelfWeightOf<T>>::create_item()20	}2122	fn create_multiple_items(amount: u32) -> Weight {23		<SelfWeightOf<T>>::create_multiple_items(amount)24	}2526	fn burn_item() -> Weight {27		<SelfWeightOf<T>>::burn_item()28	}2930	fn transfer() -> Weight {31		<SelfWeightOf<T>>::transfer()32	}3334	fn approve() -> Weight {35		<SelfWeightOf<T>>::approve()36	}3738	fn transfer_from() -> Weight {39		<SelfWeightOf<T>>::transfer_from()40	}4142	fn set_variable_metadata(_bytes: u32) -> Weight {43		<SelfWeightOf<T>>::set_variable_metadata()44	}45}4647fn map_create_data<T: Config>(48	data: nft_data_structs::CreateItemData,49	to: &T::CrossAccountId,50) -> Result<CreateItemData<T>, DispatchError> {51	match data {52		nft_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData {53			const_data: data.const_data,54			variable_data: data.variable_data,55			owner: to.clone(),56		}),57		_ => fail!(<Error<T>>::NotNonfungibleDataUsedToMintFungibleCollectionToken),58	}59}6061impl<T: Config> CommonCollectionOperations<T> for NonfungibleHandle<T> {62	fn create_item(63		&self,64		sender: T::CrossAccountId,65		to: T::CrossAccountId,66		data: nft_data_structs::CreateItemData,67	) -> DispatchResultWithPostInfo {68		with_weight(69			<Pallet<T>>::create_item(self, &sender, map_create_data(data, &to)?),70			<SelfWeightOf<T>>::create_item(),71		)72	}7374	fn create_multiple_items(75		&self,76		sender: T::CrossAccountId,77		to: T::CrossAccountId,78		data: Vec<nft_data_structs::CreateItemData>,79	) -> DispatchResultWithPostInfo {80		let data = data81			.into_iter()82			.map(|d| map_create_data::<T>(d, &to))83			.collect::<Result<Vec<_>, DispatchError>>()?;8485		let amount = data.len();86		with_weight(87			<Pallet<T>>::create_multiple_items(self, &sender, data),88			<SelfWeightOf<T>>::create_multiple_items(amount as u32),89		)90	}9192	fn burn_item(93		&self,94		sender: T::CrossAccountId,95		token: TokenId,96		amount: u128,97	) -> DispatchResultWithPostInfo {98		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);99		if amount == 1 {100			with_weight(101				<Pallet<T>>::burn(&self, &sender, token),102				<SelfWeightOf<T>>::burn_item(),103			)104		} else {105			Ok(().into())106		}107	}108109	fn transfer(110		&self,111		from: T::CrossAccountId,112		to: T::CrossAccountId,113		token: TokenId,114		amount: u128,115	) -> DispatchResultWithPostInfo {116		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);117		if amount == 1 {118			with_weight(119				<Pallet<T>>::transfer(&self, &from, &to, token),120				<SelfWeightOf<T>>::transfer(),121			)122		} else {123			Ok(().into())124		}125	}126127	fn approve(128		&self,129		sender: T::CrossAccountId,130		spender: T::CrossAccountId,131		token: TokenId,132		amount: u128,133	) -> DispatchResultWithPostInfo {134		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);135136		with_weight(137			if amount == 1 {138				<Pallet<T>>::set_allowance(&self, &sender, token, Some(&spender))139			} else {140				<Pallet<T>>::set_allowance(&self, &sender, token, None)141			},142			<SelfWeightOf<T>>::approve(),143		)144	}145146	fn transfer_from(147		&self,148		sender: T::CrossAccountId,149		from: T::CrossAccountId,150		to: T::CrossAccountId,151		token: TokenId,152		amount: u128,153	) -> DispatchResultWithPostInfo {154		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);155156		if amount == 1 {157			with_weight(158				<Pallet<T>>::transfer_from(&self, &sender, &from, &to, token),159				<SelfWeightOf<T>>::transfer_from(),160			)161		} else {162			Ok(().into())163		}164	}165166	fn set_variable_metadata(167		&self,168		sender: T::CrossAccountId,169		token: TokenId,170		data: Vec<u8>,171	) -> DispatchResultWithPostInfo {172		with_weight(173			<Pallet<T>>::set_variable_metadata(&self, &sender, token, data),174			<SelfWeightOf<T>>::set_variable_metadata(),175		)176	}177178	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {179		<Owned<T>>::iter_prefix((self.id, account.as_sub()))180			.map(|(id, _)| id)181			.collect()182	}183184	fn token_exists(&self, token: TokenId) -> bool {185		<Pallet<T>>::token_exists(self, token)186	}187188	fn last_token_id(&self) -> TokenId {189		TokenId(<TokensMinted<T>>::get(self.id))190	}191192	fn token_owner(&self, token: TokenId) -> T::CrossAccountId {193		<Owner<T>>::get((self.id, token))194	}195	fn const_metadata(&self, token: TokenId) -> Vec<u8> {196		<TokenData<T>>::get((self.id, token, DataKind::Constant))197	}198	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {199		<TokenData<T>>::get((self.id, token, DataKind::Variable))200	}201202	fn collection_tokens(&self) -> u32 {203		<Pallet<T>>::total_supply(self)204	}205206	fn account_balance(&self, account: T::CrossAccountId) -> u32 {207		<AccountBalance<T>>::get((self.id, account.as_sub()))208	}209210	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {211		if <Owner<T>>::get((self.id, token)) == account {212			1213		} else {214			0215		}216	}217218	fn allowance(219		&self,220		sender: T::CrossAccountId,221		spender: T::CrossAccountId,222		token: TokenId,223	) -> u128 {224		if <Owner<T>>::get((self.id, token)) != sender {225			0226		} else if <Allowance<T>>::get((self.id, token)) == Some(spender) {227			1228		} else {229			0230		}231	}232}
after · pallets/nonfungible/src/common.rs
1use core::marker::PhantomData;23use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};4use nft_data_structs::TokenId;5use pallet_common::{6	CommonCollectionOperations, CommonWeightInfo, account::CrossAccountId, with_weight,7};8use sp_runtime::DispatchError;9use sp_std::vec::Vec;1011use crate::{12	AccountBalance, Allowance, Config, CreateItemData, DataKind, Error, NonfungibleHandle, Owned,13	Owner, Pallet, SelfWeightOf, TokenData, weights::WeightInfo,14};1516pub struct CommonWeights<T: Config>(PhantomData<T>);17impl<T: Config> CommonWeightInfo for CommonWeights<T> {18	fn create_item() -> Weight {19		<SelfWeightOf<T>>::create_item()20	}2122	fn create_multiple_items(amount: u32) -> Weight {23		<SelfWeightOf<T>>::create_multiple_items(amount)24	}2526	fn burn_item() -> Weight {27		<SelfWeightOf<T>>::burn_item()28	}2930	fn transfer() -> Weight {31		<SelfWeightOf<T>>::transfer()32	}3334	fn approve() -> Weight {35		<SelfWeightOf<T>>::approve()36	}3738	fn transfer_from() -> Weight {39		<SelfWeightOf<T>>::transfer_from()40	}4142	fn set_variable_metadata(_bytes: u32) -> Weight {43		<SelfWeightOf<T>>::set_variable_metadata()44	}45}4647fn map_create_data<T: Config>(48	data: nft_data_structs::CreateItemData,49	to: &T::CrossAccountId,50) -> Result<CreateItemData<T>, DispatchError> {51	match data {52		nft_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData {53			const_data: data.const_data,54			variable_data: data.variable_data,55			owner: to.clone(),56		}),57		_ => fail!(<Error<T>>::NotNonfungibleDataUsedToMintFungibleCollectionToken),58	}59}6061impl<T: Config> CommonCollectionOperations<T> for NonfungibleHandle<T> {62	fn create_item(63		&self,64		sender: T::CrossAccountId,65		to: T::CrossAccountId,66		data: nft_data_structs::CreateItemData,67	) -> DispatchResultWithPostInfo {68		with_weight(69			<Pallet<T>>::create_item(self, &sender, map_create_data(data, &to)?),70			<SelfWeightOf<T>>::create_item(),71		)72	}7374	fn create_multiple_items(75		&self,76		sender: T::CrossAccountId,77		to: T::CrossAccountId,78		data: Vec<nft_data_structs::CreateItemData>,79	) -> DispatchResultWithPostInfo {80		let data = data81			.into_iter()82			.map(|d| map_create_data::<T>(d, &to))83			.collect::<Result<Vec<_>, DispatchError>>()?;8485		let amount = data.len();86		with_weight(87			<Pallet<T>>::create_multiple_items(self, &sender, data),88			<SelfWeightOf<T>>::create_multiple_items(amount as u32),89		)90	}9192	fn burn_item(93		&self,94		sender: T::CrossAccountId,95		token: TokenId,96		amount: u128,97	) -> DispatchResultWithPostInfo {98		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);99		if amount == 1 {100			with_weight(101				<Pallet<T>>::burn(&self, &sender, token),102				<SelfWeightOf<T>>::burn_item(),103			)104		} else {105			Ok(().into())106		}107	}108109	fn transfer(110		&self,111		from: T::CrossAccountId,112		to: T::CrossAccountId,113		token: TokenId,114		amount: u128,115	) -> DispatchResultWithPostInfo {116		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);117		if amount == 1 {118			with_weight(119				<Pallet<T>>::transfer(&self, &from, &to, token),120				<SelfWeightOf<T>>::transfer(),121			)122		} else {123			Ok(().into())124		}125	}126127	fn approve(128		&self,129		sender: T::CrossAccountId,130		spender: T::CrossAccountId,131		token: TokenId,132		amount: u128,133	) -> DispatchResultWithPostInfo {134		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);135136		with_weight(137			if amount == 1 {138				<Pallet<T>>::set_allowance(&self, &sender, token, Some(&spender))139			} else {140				<Pallet<T>>::set_allowance(&self, &sender, token, None)141			},142			<SelfWeightOf<T>>::approve(),143		)144	}145146	fn transfer_from(147		&self,148		sender: T::CrossAccountId,149		from: T::CrossAccountId,150		to: T::CrossAccountId,151		token: TokenId,152		amount: u128,153	) -> DispatchResultWithPostInfo {154		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);155156		if amount == 1 {157			with_weight(158				<Pallet<T>>::transfer_from(&self, &sender, &from, &to, token),159				<SelfWeightOf<T>>::transfer_from(),160			)161		} else {162			Ok(().into())163		}164	}165166	fn set_variable_metadata(167		&self,168		sender: T::CrossAccountId,169		token: TokenId,170		data: Vec<u8>,171	) -> DispatchResultWithPostInfo {172		with_weight(173			<Pallet<T>>::set_variable_metadata(&self, &sender, token, data),174			<SelfWeightOf<T>>::set_variable_metadata(),175		)176	}177178	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {179		<Owned<T>>::iter_prefix((self.id, account.as_sub()))180			.map(|(id, _)| id)181			.collect()182	}183184	fn token_exists(&self, token: TokenId) -> bool {185		<Pallet<T>>::token_exists(self, token)186	}187188	fn last_token_id(&self) -> TokenId {189		TokenId(<TokensMinted<T>>::get(self.id))190	}191192	fn token_owner(&self, token: TokenId) -> T::CrossAccountId {193		<TokenData<T>>::get((self.id, token))194			.map(|t| t.owner)195			.unwrap_or_default()196	}197	fn const_metadata(&self, token: TokenId) -> Vec<u8> {198		<TokenData<T>>::get((self.id, token))199			.map(|t| t.const_data.clone())200			.unwrap_or_default()201	}202	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {203		<TokenData<T>>::get((self.id, token))204			.map(|t| t.variable_data.clone())205			.unwrap_or_default()206	}207208	fn collection_tokens(&self) -> u32 {209		<Pallet<T>>::total_supply(self)210	}211212	fn account_balance(&self, account: T::CrossAccountId) -> u32 {213		<AccountBalance<T>>::get((self.id, account.as_sub()))214	}215216	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {217		if <TokenData<T>>::get((self.id, token))218			.map(|a| a.owner == account)219			.unwrap_or(false)220		{221			1222		} else {223			0224		}225	}226227	fn allowance(228		&self,229		sender: T::CrossAccountId,230		spender: T::CrossAccountId,231		token: TokenId,232	) -> u128 {233		if <TokenData<T>>::get((self.id, token))234			.map(|a| a.owner == sender)235			.unwrap_or(false)236		{237			0238		} else if <Allowance<T>>::get((self.id, token)) == Some(spender) {239			1240		} else {241			0242		}243	}244}
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
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -13,6 +13,7 @@
 use sp_std::{vec::Vec, vec};
 use core::ops::Deref;
 use sp_std::collections::btree_map::BTreeMap;
+use codec::{Encode, Decode};
 
 pub use pallet::*;
 pub mod benchmarking;
@@ -27,10 +28,17 @@
 }
 pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
 
+#[derive(Encode, Decode)]
+pub struct ItemData<T: Config> {
+	pub const_data: Vec<u8>,
+	pub variable_data: Vec<u8>,
+	pub owner: T::CrossAccountId,
+}
+
 #[frame_support::pallet]
 pub mod pallet {
+	use super::*;
 	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};
-	use sp_std::vec::Vec;
 	use nft_data_structs::{CollectionId, TokenId};
 	use super::weights::WeightInfo;
 
@@ -58,29 +66,13 @@
 	pub(super) type TokensBurnt<T: Config> =
 		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
 
-	#[derive(Encode, Decode)]
-	pub enum DataKind {
-		Constant,
-		Variable,
-	}
-
 	#[pallet::storage]
 	pub(super) type TokenData<T: Config> = StorageNMap<
-		Key = (
-			Key<Twox64Concat, CollectionId>,
-			Key<Twox64Concat, TokenId>,
-			Key<Identity, DataKind>,
-		),
-		Value = Vec<u8>,
-		QueryKind = ValueQuery,
+		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
+		Value = ItemData<T>,
+		QueryKind = OptionQuery,
 	>;
 
-	#[pallet::storage]
-	pub(super) type Owner<T: Config> = StorageNMap<
-		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
-		Value = T::CrossAccountId,
-		QueryKind = ValueQuery,
-	>;
 	/// Used to enumerate tokens owned by account
 	#[pallet::storage]
 	pub(super) type Owned<T: Config> = StorageNMap<
@@ -133,29 +125,7 @@
 		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)
 	}
 	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {
-		<Owner<T>>::contains_key((collection.id, token))
-	}
-	pub fn ensure_owner(
-		collection: &NonfungibleHandle<T>,
-		token: TokenId,
-		sender: &T::CrossAccountId,
-	) -> DispatchResult {
-		ensure!(
-			&<Owner<T>>::get((collection.id, token)) == sender,
-			<CommonError<T>>::NoPermission
-		);
-		Ok(())
-	}
-	pub fn item_owner(
-		collection: &NonfungibleHandle<T>,
-		token: TokenId,
-	) -> Result<T::CrossAccountId, DispatchError> {
-		let owner = <Owner<T>>::get((collection.id, token));
-		ensure!(
-			owner != T::CrossAccountId::default(),
-			<CommonError<T>>::TokenNotFound
-		);
-		Ok(owner)
+		<TokenData<T>>::contains_key((collection.id, token))
 	}
 }
 
@@ -174,11 +144,10 @@
 
 		PalletCommon::destroy_collection(collection.0, sender)?;
 
-		<Owner<T>>::remove_prefix((id,), None);
+		<TokenData<T>>::remove_prefix((id,), None);
 		<Owned<T>>::remove_prefix((id,), None);
 		<TokensMinted<T>>::remove(id);
 		<TokensBurnt<T>>::remove(id);
-		<TokenData<T>>::remove_prefix((id,), None);
 		<Allowance<T>>::remove_prefix((id,), None);
 		<AccountBalance<T>>::remove_prefix((id,), None);
 		Ok(())
@@ -189,9 +158,10 @@
 		sender: &T::CrossAccountId,
 		token: TokenId,
 	) -> DispatchResult {
-		let token_owner = <Pallet<T>>::item_owner(collection, token)?;
+		let token_data = <TokenData<T>>::get((collection.id, token))
+			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
 		ensure!(
-			&token_owner == sender
+			&token_data.owner == sender
 				|| (collection.limits.owner_can_transfer
 					&& collection.is_owner_or_admin(sender)?),
 			<CommonError<T>>::NoPermission
@@ -207,21 +177,30 @@
 
 		// =========
 
-		<Owner<T>>::remove((collection.id, token));
-		<Owned<T>>::remove((collection.id, token_owner.as_sub(), token));
+		<Owned<T>>::remove((collection.id, token_data.owner.as_sub(), token));
 		<TokensBurnt<T>>::insert(collection.id, burnt);
-		<TokenData<T>>::remove_prefix((collection.id, token), None);
-		<Allowance<T>>::remove((collection.id, token));
+		<TokenData<T>>::remove((collection.id, token));
+		let old_spender = <Allowance<T>>::take((collection.id, token));
 
+		if let Some(old_spender) = old_spender {
+			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
+				collection.id,
+				token,
+				sender.clone(),
+				old_spender.clone(),
+				0,
+			));
+		}
+
 		collection.log_infallible(ERC721Events::Transfer {
-			from: *token_owner.as_eth(),
+			from: *token_data.owner.as_eth(),
 			to: H160::default(),
 			token_id: token.into(),
 		});
 		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
 			collection.id,
 			token,
-			token_owner,
+			token_data.owner,
 			1,
 		));
 		return Ok(());
@@ -238,9 +217,10 @@
 			<CommonError<T>>::TransferNotAllowed
 		);
 
-		let token_owner = <Pallet<T>>::item_owner(collection, token)?;
+		let token_data = <TokenData<T>>::get((collection.id, token))
+			.ok_or_else(|| <CommonError<T>>::TokenNotFound)?;
 		ensure!(
-			&token_owner == from
+			&token_data.owner == from
 				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),
 			<CommonError<T>>::NoPermission
 		);
@@ -274,6 +254,14 @@
 
 		// =========
 
+		<TokenData<T>>::insert(
+			(collection.id, token),
+			ItemData {
+				owner: to.clone(),
+				..token_data
+			},
+		);
+
 		if let Some(balance_to) = balance_to {
 			// from != to
 			if balance_from == 0 {
@@ -286,7 +274,6 @@
 			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);
 		}
 		Self::set_allowance_unchecked(collection, from, token, None);
-		<Owner<T>>::insert((collection.id, token), &to);
 
 		collection.log_infallible(ERC721Events::Transfer {
 			from: *from.as_eth(),
@@ -364,18 +351,16 @@
 			<AccountBalance<T>>::insert((collection.id, account), balance);
 		}
 		for (i, data) in data.into_iter().enumerate() {
-			let token = first_token + i as u32;
+			let token = first_token + i as u32 + 1;
 
-			if !data.const_data.is_empty() {
-				<TokenData<T>>::insert((collection.id, token, DataKind::Constant), data.const_data);
-			}
-			if !data.variable_data.is_empty() {
-				<TokenData<T>>::insert(
-					(collection.id, token, DataKind::Variable),
-					data.variable_data,
-				);
-			}
-			<Owner<T>>::insert((collection.id, token), &data.owner);
+			<TokenData<T>>::insert(
+				(collection.id, token),
+				ItemData {
+					const_data: data.const_data.into(),
+					variable_data: data.variable_data.into(),
+					owner: data.owner.clone(),
+				},
+			);
 			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);
 
 			collection.log_infallible(ERC721Events::Transfer {
@@ -383,6 +368,12 @@
 				to: *data.owner.as_eth(),
 				token_id: token.into(),
 			});
+			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
+				collection.id,
+				TokenId(token),
+				data.owner.clone(),
+				1,
+			));
 		}
 		Ok(())
 	}
@@ -462,8 +453,9 @@
 		if let Some(spender) = spender {
 			<PalletCommon<T>>::ensure_correct_receiver(spender)?;
 		}
-		let token_owner = Self::item_owner(collection, token)?;
-		if &token_owner != sender {
+		let token_data =
+			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
+		if &token_data.owner != sender {
 			ensure!(
 				collection.ignores_owned_amount(sender)?,
 				<CommonError<T>>::CantApproveMoreThanOwned
@@ -515,14 +507,21 @@
 			data.len() as u32 <= CUSTOM_DATA_LIMIT,
 			<CommonError<T>>::TokenVariableDataLimitExceeded
 		);
-		let item_owner = Self::item_owner(collection, token)?;
-		collection.check_can_update_meta(sender, &item_owner)?;
+		let token_data =
+			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
+		collection.check_can_update_meta(sender, &token_data.owner)?;
 
 		collection.consume_sstore()?;
 
 		// =========
 
-		<TokenData<T>>::insert((collection.id, token, DataKind::Variable), data);
+		<TokenData<T>>::insert(
+			(collection.id, token),
+			ItemData {
+				variable_data: data,
+				..token_data
+			},
+		);
 		Ok(())
 	}