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
before · pallets/nonfungible/src/erc.rs
1use core::{2	char::{REPLACEMENT_CHARACTER, decode_utf16},3	convert::TryInto,4};5use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*};6use frame_support::BoundedVec;7use nft_data_structs::TokenId;8use pallet_evm_coder_substrate::dispatch_to_evm;9use sp_core::{H160, U256};10use sp_std::{vec::Vec, vec};11use pallet_common::{account::CrossAccountId, erc::CommonEvmHandler};12use pallet_evm_coder_substrate::call_internal;13use pallet_common::erc::PrecompileOutput;1415use crate::{16	AccountBalance, Config, CreateItemData, DataKind, NonfungibleHandle, Owner, Pallet, TokenData,17	TokensMinted,18};1920#[derive(ToLog)]21pub enum ERC721Events {22	Transfer {23		#[indexed]24		from: address,25		#[indexed]26		to: address,27		#[indexed]28		token_id: uint256,29	},30	Approval {31		#[indexed]32		owner: address,33		#[indexed]34		approved: address,35		#[indexed]36		token_id: uint256,37	},38	#[allow(dead_code)]39	ApprovalForAll {40		#[indexed]41		owner: address,42		#[indexed]43		operator: address,44		approved: bool,45	},46}4748#[derive(ToLog)]49pub enum ERC721MintableEvents {50	#[allow(dead_code)]51	MintingFinished {},52}5354#[solidity_interface(name = "ERC721Metadata")]55impl<T: Config> NonfungibleHandle<T> {56	fn name(&self) -> Result<string> {57		Ok(decode_utf16(self.name.iter().copied())58			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))59			.collect::<string>())60	}61	fn symbol(&self) -> Result<string> {62		Ok(string::from_utf8_lossy(&self.token_prefix).into())63	}6465	#[solidity(rename_selector = "tokenURI")]66	fn token_uri(&self, token_id: uint256) -> Result<string> {67		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;68		Ok(string::from_utf8_lossy(&<TokenData<T>>::get((69			self.id,70			token_id,71			DataKind::Constant,72		)))73		.into())74	}75}7677#[solidity_interface(name = "ERC721Enumerable")]78impl<T: Config> NonfungibleHandle<T> {79	fn token_by_index(&self, index: uint256) -> Result<uint256> {80		Ok(index)81	}8283	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {84		// TODO: Not implemetable85		Err("not implemented".into())86	}8788	fn total_supply(&self) -> Result<uint256> {89		Ok(<Pallet<T>>::total_supply(self).into())90	}91}9293#[solidity_interface(name = "ERC721", events(ERC721Events))]94impl<T: Config> NonfungibleHandle<T> {95	fn balance_of(&self, owner: address) -> Result<uint256> {96		let owner = T::CrossAccountId::from_eth(owner);97		let balance = <AccountBalance<T>>::get((self.id, owner.as_sub()));98		Ok(balance.into())99	}100	fn owner_of(&self, token_id: uint256) -> Result<address> {101		let token: TokenId = token_id.try_into()?;102		Ok(*<Owner<T>>::get((self.id, token)).as_eth())103	}104	fn safe_transfer_from_with_data(105		&mut self,106		_from: address,107		_to: address,108		_token_id: uint256,109		_data: bytes,110		_value: value,111	) -> Result<void> {112		// TODO: Not implemetable113		Err("not implemented".into())114	}115	fn safe_transfer_from(116		&mut self,117		_from: address,118		_to: address,119		_token_id: uint256,120		_value: value,121	) -> Result<void> {122		// TODO: Not implemetable123		Err("not implemented".into())124	}125126	fn transfer_from(127		&mut self,128		caller: caller,129		from: address,130		to: address,131		token_id: uint256,132		_value: value,133	) -> Result<void> {134		let caller = T::CrossAccountId::from_eth(caller);135		let from = T::CrossAccountId::from_eth(from);136		let to = T::CrossAccountId::from_eth(to);137		let token = token_id.try_into()?;138139		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token)140			.map_err(dispatch_to_evm::<T>)?;141		Ok(())142	}143144	fn approve(145		&mut self,146		caller: caller,147		approved: address,148		token_id: uint256,149		_value: value,150	) -> Result<void> {151		let caller = T::CrossAccountId::from_eth(caller);152		let approved = T::CrossAccountId::from_eth(approved);153		let token = token_id.try_into()?;154155		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))156			.map_err(dispatch_to_evm::<T>)?;157		Ok(())158	}159160	fn set_approval_for_all(161		&mut self,162		_caller: caller,163		_operator: address,164		_approved: bool,165	) -> Result<void> {166		// TODO: Not implemetable167		Err("not implemented".into())168	}169170	fn get_approved(&self, _token_id: uint256) -> Result<address> {171		// TODO: Not implemetable172		Err("not implemented".into())173	}174175	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {176		// TODO: Not implemetable177		Err("not implemented".into())178	}179}180181#[solidity_interface(name = "ERC721Burnable")]182impl<T: Config> NonfungibleHandle<T> {183	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {184		let caller = T::CrossAccountId::from_eth(caller);185		let token = token_id.try_into()?;186187		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;188		Ok(())189	}190}191192#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]193impl<T: Config> NonfungibleHandle<T> {194	fn minting_finished(&self) -> Result<bool> {195		Ok(false)196	}197198	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {199		let caller = T::CrossAccountId::from_eth(caller);200		let to = T::CrossAccountId::from_eth(to);201		let token_id: u32 = token_id.try_into()?;202		if <TokensMinted<T>>::get(self.id)203			.checked_add(1)204			.ok_or("item id overflow")?205			!= token_id206		{207			return Err("item id should be next".into());208		}209210		<Pallet<T>>::create_item(211			self,212			&caller,213			CreateItemData {214				const_data: BoundedVec::default(),215				variable_data: BoundedVec::default(),216				owner: to,217			},218		)219		.map_err(dispatch_to_evm::<T>)?;220221		Ok(true)222	}223224	#[solidity(rename_selector = "mintWithTokenURI")]225	fn mint_with_token_uri(226		&mut self,227		caller: caller,228		to: address,229		token_id: uint256,230		token_uri: string,231	) -> Result<bool> {232		let caller = T::CrossAccountId::from_eth(caller);233		let to = T::CrossAccountId::from_eth(to);234		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;235		if <TokensMinted<T>>::get(self.id)236			.checked_add(1)237			.ok_or("item id overflow")?238			!= token_id239		{240			return Err("item id should be next".into());241		}242243		<Pallet<T>>::create_item(244			self,245			&caller,246			CreateItemData {247				const_data: Vec::<u8>::from(token_uri)248					.try_into()249					.map_err(|_| "token uri is too long")?,250				variable_data: BoundedVec::default(),251				owner: to,252			},253		)254		.map_err(dispatch_to_evm::<T>)?;255		Ok(true)256	}257258	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {259		Err("not implementable".into())260	}261}262263#[solidity_interface(name = "ERC721UniqueExtensions")]264impl<T: Config> NonfungibleHandle<T> {265	#[solidity(rename_selector = "transfer")]266	fn transfer_nft(267		&mut self,268		caller: caller,269		to: address,270		token_id: uint256,271		_value: value,272	) -> Result<void> {273		let caller = T::CrossAccountId::from_eth(caller);274		let to = T::CrossAccountId::from_eth(to);275		let token = token_id.try_into()?;276277		<Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;278		Ok(())279	}280281	fn next_token_id(&self) -> Result<uint256> {282		Ok(<TokensMinted<T>>::get(self.id)283			.checked_add(1)284			.ok_or("item id overflow")?285			.into())286	}287288	fn set_variable_metadata(289		&mut self,290		caller: caller,291		token_id: uint256,292		data: bytes,293	) -> Result<void> {294		let caller = T::CrossAccountId::from_eth(caller);295		let token = token_id.try_into()?;296297		<Pallet<T>>::set_variable_metadata(self, &caller, token, data)298			.map_err(dispatch_to_evm::<T>)?;299		Ok(())300	}301302	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {303		let token: TokenId = token_id.try_into()?;304305		Ok(<TokenData<T>>::get((self.id, token, DataKind::Variable)))306	}307308	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {309		let caller = T::CrossAccountId::from_eth(caller);310		let to = T::CrossAccountId::from_eth(to);311		let mut expected_index = <TokensMinted<T>>::get(self.id)312			.checked_add(1)313			.ok_or("item id overflow")?;314315		let total_tokens = token_ids.len();316		for id in token_ids.into_iter() {317			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;318			if id != expected_index {319				return Err("item id should be next".into());320			}321			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;322		}323		let data = (0..total_tokens)324			.map(|_| CreateItemData {325				const_data: BoundedVec::default(),326				variable_data: BoundedVec::default(),327				owner: to.clone(),328			})329			.collect();330331		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;332		Ok(true)333	}334335	#[solidity(rename_selector = "mintBulkWithTokenURI")]336	fn mint_bulk_with_token_uri(337		&mut self,338		caller: caller,339		to: address,340		tokens: Vec<(uint256, string)>,341	) -> Result<bool> {342		let caller = T::CrossAccountId::from_eth(caller);343		let to = T::CrossAccountId::from_eth(to);344		let mut expected_index = <TokensMinted<T>>::get(self.id)345			.checked_add(1)346			.ok_or("item id overflow")?;347348		let mut data = Vec::with_capacity(tokens.len());349		for (id, token_uri) in tokens {350			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;351			if id != expected_index {352				panic!("item id should be next ({}) but got {}", expected_index, id);353			}354			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;355356			data.push(CreateItemData {357				const_data: Vec::<u8>::from(token_uri)358					.try_into()359					.map_err(|_| "token uri is too long")?,360				variable_data: vec![].try_into().unwrap(),361				owner: to.clone(),362			});363		}364365		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;366		Ok(true)367	}368}369370#[solidity_interface(371	name = "UniqueNFT",372	is(373		ERC721,374		ERC721Metadata,375		ERC721Enumerable,376		ERC721UniqueExtensions,377		ERC721Mintable,378		ERC721Burnable,379	)380)]381impl<T: Config> NonfungibleHandle<T> {}382383// Not a tests, but code generators384generate_stubgen!(gen_impl, UniqueNFTCall, true);385generate_stubgen!(gen_iface, UniqueNFTCall, false);386387pub const CODE: &[u8] = include_bytes!("./stubs/UniqueNFT.raw");388389impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {390	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");391392	fn call(mut self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileOutput> {393		let result = call_internal::<UniqueNFTCall, _>(*source, &mut self, value, input);394		self.0.recorder.evm_to_precompile_output(result)395	}396}
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(())
 	}