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

difftreelog

feat add token children

Daniel Shiposha2022-05-26parent: #459d9af.patch.diff
in: master

6 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1237,6 +1237,18 @@
 		budget: &dyn Budget,
 	) -> DispatchResult;
 
+	fn nest(
+		&self,
+		_under: TokenId,
+		_to_nest: (CollectionId, TokenId)
+	) {}
+
+	fn unnest(
+		&self,
+		_under: TokenId,
+		_to_nest: (CollectionId, TokenId)
+	) {}
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;
 	fn collection_tokens(&self) -> Vec<TokenId>;
 	fn token_exists(&self, token: TokenId) -> bool;
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -25,8 +25,8 @@
 	budget::Budget,
 };
 use pallet_common::{
-	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, CollectionHandle,
-	dispatch::CollectionDispatch, eth::collection_id_to_address,
+	Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
+	eth::collection_id_to_address,
 };
 use pallet_evm::Pallet as PalletEvm;
 use pallet_structure::Pallet as PalletStructure;
@@ -176,6 +176,11 @@
 
 		if balance == 0 {
 			<Balance<T>>::remove((collection.id, owner));
+			<PalletStructure<T>>::unnest_if_nested(
+				owner,
+				collection.id,
+				TokenId::default()
+			);
 		} else {
 			<Balance<T>>::insert((collection.id, owner), balance);
 		}
@@ -228,22 +233,17 @@
 		} else {
 			None
 		};
-
-		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-			let handle = <CollectionHandle<T>>::try_get(target.0)?;
-			let dispatch = T::CollectionDispatch::dispatch(handle);
-			let dispatch = dispatch.as_dyn();
 
-			dispatch.check_nesting(
-				from.clone(),
-				(collection.id, TokenId::default()),
-				target.1,
-				nesting_budget,
-			)?;
-		}
-
 		// =========
 
+		<PalletStructure<T>>::try_nest_if_sent_to_token(
+			from.clone(),
+			to,
+			collection.id,
+			TokenId::default(),
+			nesting_budget
+		)?;
+
 		if let Some(balance_to) = balance_to {
 			// from != to
 			if balance_from == 0 {
@@ -306,18 +306,13 @@
 		}
 
 		for (to, _) in balances.iter() {
-			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-				let handle = <CollectionHandle<T>>::try_get(target.0)?;
-				let dispatch = T::CollectionDispatch::dispatch(handle);
-				let dispatch = dispatch.as_dyn();
-
-				dispatch.check_nesting(
-					sender.clone(),
-					(collection.id, TokenId::default()),
-					target.1,
-					nesting_budget,
-				)?;
-			}
+			<PalletStructure<T>>::check_nesting(
+				sender.clone(),
+				to,
+				collection.id,
+				TokenId::default(),
+				nesting_budget,
+			)?;
 		}
 
 		// =========
@@ -325,7 +320,7 @@
 		<TotalSupply<T>>::insert(collection.id, total_supply);
 		for (user, amount) in balances {
 			<Balance<T>>::insert((collection.id, &user), amount);
-
+			<PalletStructure<T>>::nest_if_sent_to_token(&user, collection.id, TokenId::default());
 			<PalletEvm<T>>::deposit_log(
 				ERC20Events::Transfer {
 					from: H160::default(),
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -353,6 +353,22 @@
 		<Pallet<T>>::check_nesting(self, sender, from, under, budget)
 	}
 
+	fn nest(
+		&self,
+		under: TokenId,
+		to_nest: (CollectionId, TokenId)
+	) {
+		<Pallet<T>>::nest((self.id, under), to_nest);
+	}
+
+	fn unnest(
+		&self,
+		under: TokenId,
+		to_unnest: (CollectionId, TokenId)
+	) {
+		<Pallet<T>>::unnest((self.id, under), to_unnest);
+	}
+
 	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
 		<Owned<T>>::iter_prefix((self.id, account))
 			.map(|(id, _)| id)
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -27,7 +27,7 @@
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_common::{
 	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
-	dispatch::CollectionDispatch, eth::collection_id_to_address,
+	eth::collection_id_to_address,
 };
 use pallet_structure::Pallet as PalletStructure;
 use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
@@ -77,6 +77,8 @@
 		NotNonfungibleDataUsedToMintFungibleCollectionToken,
 		/// Used amount > 1 with NFT
 		NonfungibleItemsHaveNoAmount,
+		/// Unable to burn NFT with children
+		CantBurnNftWithChildren,
 	}
 
 	#[pallet::config]
@@ -128,7 +130,20 @@
 		QueryKind = ValueQuery,
 	>;
 
+	/// Used to enumerate token's children
 	#[pallet::storage]
+	#[pallet::getter(fn token_children)]
+	pub type TokenChildren<T: Config> = StorageNMap<
+		Key = (
+			Key<Twox64Concat, CollectionId>,
+			Key<Twox64Concat, TokenId>,
+			Key<Twox64Concat, (CollectionId, TokenId)>,
+		),
+		Value = bool,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
 	pub type AccountBalance<T: Config> = StorageNMap<
 		Key = (
 			Key<Twox64Concat, CollectionId>,
@@ -283,6 +298,7 @@
 		PalletCommon::destroy_collection(collection.0, sender)?;
 
 		<TokenData<T>>::remove_prefix((id,), None);
+		<TokenChildren<T>>::remove_prefix((id,), None);
 		<Owned<T>>::remove_prefix((id,), None);
 		<TokensMinted<T>>::remove(id);
 		<TokensBurnt<T>>::remove(id);
@@ -308,6 +324,10 @@
 			collection.check_allowlist(sender)?;
 		}
 
+		if Self::token_has_children(collection.id, token) {
+			return Err(<Error<T>>::CantBurnNftWithChildren.into());
+		}
+
 		let burnt = <TokensBurnt<T>>::get(collection.id)
 			.checked_add(1)
 			.ok_or(ArithmeticError::Overflow)?;
@@ -321,6 +341,11 @@
 		} else {
 			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);
 		}
+
+		if let Some(owner) = T::CrossTokenAddressMapping::address_to_token(&token_data.owner) {
+			Self::unnest(owner, (collection.id, token));
+		}
+
 		// =========
 
 		<Owned<T>>::remove((collection.id, &token_data.owner, token));
@@ -554,18 +579,13 @@
 			None
 		};
 
-		if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
-			let handle = <CollectionHandle<T>>::try_get(target.0)?;
-			let dispatch = T::CollectionDispatch::dispatch(handle);
-			let dispatch = dispatch.as_dyn();
-
-			dispatch.check_nesting(
-				from.clone(),
-				(collection.id, token),
-				target.1,
-				nesting_budget,
-			)?;
-		}
+		<PalletStructure<T>>::try_nest_if_sent_to_token(
+			from.clone(),
+			to,
+			collection.id,
+			token,
+			nesting_budget
+		)?;
 
 		// =========
 
@@ -654,17 +674,14 @@
 
 		for (i, data) in data.iter().enumerate() {
 			let token = TokenId(first_token + i as u32 + 1);
-			if let Some(target) = T::CrossTokenAddressMapping::address_to_token(&data.owner) {
-				let handle = <CollectionHandle<T>>::try_get(target.0)?;
-				let dispatch = T::CollectionDispatch::dispatch(handle);
-				let dispatch = dispatch.as_dyn();
-				dispatch.check_nesting(
-					sender.clone(),
-					(collection.id, token),
-					target.1,
-					nesting_budget,
-				)?;
-			}
+
+			<PalletStructure<T>>::check_nesting(
+				sender.clone(),
+				&data.owner,
+				collection.id,
+				token,
+				nesting_budget,
+			)?;
 		}
 
 		// =========
@@ -681,6 +698,8 @@
 					},
 				);
 
+				<PalletStructure<T>>::nest_if_sent_to_token(&data.owner, collection.id, TokenId(token));
+
 				if let Err(e) = Self::set_token_properties(
 					collection,
 					sender,
@@ -928,6 +947,29 @@
 		Ok(())
 	}
 
+	fn nest(
+		under: (CollectionId, TokenId),
+		to_nest: (CollectionId, TokenId),
+	) {
+		<TokenChildren<T>>::insert(
+			(under.0, under.1, (to_nest.0, to_nest.1)),
+			true
+		);
+	}
+
+	fn unnest(
+		under: (CollectionId, TokenId),
+		to_unnest: (CollectionId, TokenId),
+	) {
+		<TokenChildren<T>>::remove(
+			(under.0, under.1, to_unnest)
+		);
+	}
+
+	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {
+		<TokenChildren<T>>::iter_prefix((collection_id, token_id)).next().is_some()
+	}
+
 	/// Delegated to `create_multiple_items`
 	pub fn create_item(
 		collection: &NonfungibleHandle<T>,
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
23};23};
24use pallet_evm::account::CrossAccountId;24use pallet_evm::account::CrossAccountId;
25use pallet_common::{25use pallet_common::{
26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, CollectionHandle,26 Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
27 dispatch::CollectionDispatch,
28};27};
29use pallet_structure::Pallet as PalletStructure;28use pallet_structure::Pallet as PalletStructure;
30use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};29use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
265 // =========264 // =========
266265
267 <Owned<T>>::remove((collection.id, owner, token));266 <Owned<T>>::remove((collection.id, owner, token));
267 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);
268 <AccountBalance<T>>::insert((collection.id, owner), account_balance);268 <AccountBalance<T>>::insert((collection.id, owner), account_balance);
269 Self::burn_token(collection, token)?;269 Self::burn_token(collection, token)?;
270 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(270 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
292292
293 if balance == 0 {293 if balance == 0 {
294 <Owned<T>>::remove((collection.id, owner, token));294 <Owned<T>>::remove((collection.id, owner, token));
295 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);
295 <Balance<T>>::remove((collection.id, token, owner));296 <Balance<T>>::remove((collection.id, token, owner));
296 <AccountBalance<T>>::insert((collection.id, owner), account_balance);297 <AccountBalance<T>>::insert((collection.id, owner), account_balance);
297 } else {298 } else {
372 None373 None
373 };374 };
374375
375 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {376 // =========
376 let handle = <CollectionHandle<T>>::try_get(target.0)?;
377 let dispatch = T::CollectionDispatch::dispatch(handle);
378 let dispatch = dispatch.as_dyn();
379377
380 dispatch.check_nesting(378 <PalletStructure<T>>::try_nest_if_sent_to_token(
381 from.clone(),379 from.clone(),
380 to,
382 (collection.id, token),381 collection.id,
383 target.1,382 token,
384 nesting_budget,383 nesting_budget
385 )?;384 )?;
386 }
387
388 // =========
389385
390 if let Some(balance_to) = balance_to {386 if let Some(balance_to) = balance_to {
391 // from != to387 // from != to
488 for (i, token) in data.iter().enumerate() {484 for (i, token) in data.iter().enumerate() {
489 let token_id = TokenId(first_token_id + i as u32 + 1);485 let token_id = TokenId(first_token_id + i as u32 + 1);
490 for (to, _) in token.users.iter() {486 for (to, _) in token.users.iter() {
491 if let Some(target) = T::CrossTokenAddressMapping::address_to_token(to) {
492 let handle = <CollectionHandle<T>>::try_get(target.0)?;
493 let dispatch = T::CollectionDispatch::dispatch(handle);
494 let dispatch = dispatch.as_dyn();
495487
496 dispatch.check_nesting(488 <PalletStructure<T>>::check_nesting(
497 sender.clone(),489 sender.clone(),
490 to,
498 (collection.id, token_id),491 collection.id,
499 target.1,492 token_id,
500 nesting_budget,493 nesting_budget,
501 )?;494 )?;
502 }
503 }495 }
504 }496 }
505497
525 }518 }
526 <Balance<T>>::insert((collection.id, token_id, &user), amount);519 <Balance<T>>::insert((collection.id, token_id, &user), amount);
527 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);520 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);
521 <PalletStructure<T>>::nest_if_sent_to_token(&user, collection.id, TokenId(token_id));
522
528 // TODO: ERC20 transfer event523 // TODO: ERC20 transfer event
529 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(524 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -1,8 +1,9 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
+use pallet_common::CommonCollectionOperations;
 use sp_std::collections::btree_set::BTreeSet;
 
-use frame_support::dispatch::DispatchError;
+use frame_support::dispatch::{DispatchError, DispatchResult};
 use frame_support::fail;
 pub use pallet::*;
 use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
@@ -174,7 +175,7 @@
 				v if v == target_parent => return Ok(true),
 				// Token is owned by other user
 				Parent::User(_) => return Ok(false),
-				Parent::TokenNotFound => return Ok(false),
+				Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
 				// Continue parent chain
 				Parent::Token(_, _) => {}
 			}
@@ -182,4 +183,113 @@
 
 		Err(<Error<T>>::DepthLimit.into())
 	}
+
+	pub fn check_nesting(
+		from: T::CrossAccountId,
+		under: &T::CrossAccountId,
+		collection_id: CollectionId,
+		token_id: TokenId,
+		nesting_budget: &dyn Budget
+	) -> DispatchResult {
+		Self::try_dispatched(
+			under,
+			|d, parent_id| d.check_nesting(
+				from,
+				(collection_id, token_id),
+				parent_id,
+				nesting_budget
+			)
+		)
+	}
+
+	pub fn try_nest_if_sent_to_token(
+		from: T::CrossAccountId,
+		under: &T::CrossAccountId,
+		collection_id: CollectionId,
+		token_id: TokenId,
+		nesting_budget: &dyn Budget
+	) -> DispatchResult {
+		Self::try_dispatched(
+			under,
+			|d, parent_id| {
+				d.check_nesting(
+					from,
+					(collection_id, token_id),
+					parent_id,
+					nesting_budget
+				)?;
+
+				d.nest(parent_id, (collection_id, token_id));
+
+				Ok(())
+			}
+		)
+	}
+
+	pub fn nest_if_sent_to_token(
+		owner: &T::CrossAccountId,
+		collection_id: CollectionId,
+		token_id: TokenId
+	) {
+		Self::dispatched(
+			owner,
+			|d, parent_id| d.nest(
+				parent_id,
+				(collection_id, token_id)
+			)
+		);
+	}
+
+	pub fn unnest_if_nested(
+		owner: &T::CrossAccountId,
+		collection_id: CollectionId,
+		token_id: TokenId
+	) {
+		Self::dispatched(
+			owner,
+			|d, parent_id| d.unnest(
+			parent_id,
+			(collection_id, token_id)
+			)
+		);
+	}
+
+	fn dispatched(
+		account: &T::CrossAccountId,
+		action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId)
+	) {
+		Self::try_dispatched(
+			account,
+			|d, id| {
+				action(d, id);
+				Ok(())
+			}
+		).unwrap();
+	}
+
+	fn try_dispatched(
+		account: &T::CrossAccountId,
+		action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult
+	) -> DispatchResult {
+		let account = T::CrossTokenAddressMapping::address_to_token(account);
+
+		if account.is_none() {
+			return Ok(());
+		}
+
+		let account = account.unwrap();
+
+		let handle = <CollectionHandle<T>>::try_get(account.0);
+
+		if handle.is_err() {
+			return Ok(());
+		}
+
+		let handle = handle.unwrap();
+
+		let dispatch = T::CollectionDispatch::dispatch(handle);
+		let dispatch = dispatch.as_dyn();
+
+		action(dispatch, account.1)
+	}
 }