From f8a228859ea1c530a224b7606934a79f56058428 Mon Sep 17 00:00:00 2001
From: Shawn Tabrizi <shawntabrizi@gmail.com>
Date: Sun, 26 Sep 2021 18:05:01 -0400
Subject: [PATCH] Fungibles and Non-Fungibles Create and Destroy Traits +
 Assets and Uniques Implementation (#9844)

* refactor `do_destroy`

* destroy trait

* refactor do_force_create

* impl create trait

* do not bleed weight into api

* Do the same for uniques

* add docs
---
 substrate/frame/assets/src/functions.rs       | 84 +++++++++++++++++++
 substrate/frame/assets/src/impl_fungibles.rs  | 27 ++++++
 substrate/frame/assets/src/lib.rs             | 65 ++------------
 .../support/src/traits/tokens/fungibles.rs    | 36 ++++++++
 .../support/src/traits/tokens/nonfungibles.rs | 27 +++++-
 substrate/frame/uniques/src/functions.rs      | 35 ++++++++
 .../frame/uniques/src/impl_nonfungibles.rs    | 23 +++--
 substrate/frame/uniques/src/lib.rs            | 36 ++------
 8 files changed, 244 insertions(+), 89 deletions(-)

diff --git a/substrate/frame/assets/src/functions.rs b/substrate/frame/assets/src/functions.rs
index 81b490eaf87..ae31b8e3951 100644
--- a/substrate/frame/assets/src/functions.rs
+++ b/substrate/frame/assets/src/functions.rs
@@ -478,4 +478,88 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
 		Self::deposit_event(Event::Transferred(id, source.clone(), dest.clone(), credit));
 		Ok(credit)
 	}
+
+	/// Create a new asset without taking a deposit.
+	///
+	/// * `id`: The `AssetId` you want the new asset to have. Must not already be in use.
+	/// * `owner`: The owner, issuer, admin, and freezer of this asset upon creation.
+	/// * `is_sufficient`: Whether this asset needs users to have an existential deposit to hold
+	///   this asset.
+	/// * `min_balance`: The minimum balance a user is allowed to have of this asset before they are
+	///   considered dust and cleaned up.
+	pub(super) fn do_force_create(
+		id: T::AssetId,
+		owner: T::AccountId,
+		is_sufficient: bool,
+		min_balance: T::Balance,
+	) -> DispatchResult {
+		ensure!(!Asset::<T, I>::contains_key(id), Error::<T, I>::InUse);
+		ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
+
+		Asset::<T, I>::insert(
+			id,
+			AssetDetails {
+				owner: owner.clone(),
+				issuer: owner.clone(),
+				admin: owner.clone(),
+				freezer: owner.clone(),
+				supply: Zero::zero(),
+				deposit: Zero::zero(),
+				min_balance,
+				is_sufficient,
+				accounts: 0,
+				sufficients: 0,
+				approvals: 0,
+				is_frozen: false,
+			},
+		);
+		Self::deposit_event(Event::ForceCreated(id, owner));
+		Ok(())
+	}
+
+	/// Destroy an existing asset.
+	///
+	/// * `id`: The asset you want to destroy.
+	/// * `witness`: Witness data needed about the current state of the asset, used to confirm
+	///   complexity of the operation.
+	/// * `maybe_check_owner`: An optional check before destroying the asset, if the provided
+	///   account is the owner of that asset. Can be used for authorization checks.
+	pub(super) fn do_destroy(
+		id: T::AssetId,
+		witness: DestroyWitness,
+		maybe_check_owner: Option<T::AccountId>,
+	) -> Result<DestroyWitness, DispatchError> {
+		Asset::<T, I>::try_mutate_exists(id, |maybe_details| {
+			let mut details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
+			if let Some(check_owner) = maybe_check_owner {
+				ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
+			}
+			ensure!(details.accounts <= witness.accounts, Error::<T, I>::BadWitness);
+			ensure!(details.sufficients <= witness.sufficients, Error::<T, I>::BadWitness);
+			ensure!(details.approvals <= witness.approvals, Error::<T, I>::BadWitness);
+
+			for (who, v) in Account::<T, I>::drain_prefix(id) {
+				Self::dead_account(id, &who, &mut details, v.sufficient);
+			}
+			debug_assert_eq!(details.accounts, 0);
+			debug_assert_eq!(details.sufficients, 0);
+
+			let metadata = Metadata::<T, I>::take(&id);
+			T::Currency::unreserve(
+				&details.owner,
+				details.deposit.saturating_add(metadata.deposit),
+			);
+
+			for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((&id,)) {
+				T::Currency::unreserve(&owner, approval.deposit);
+			}
+			Self::deposit_event(Event::Destroyed(id));
+
+			Ok(DestroyWitness {
+				accounts: details.accounts,
+				sufficients: details.sufficients,
+				approvals: details.approvals,
+			})
+		})
+	}
 }
diff --git a/substrate/frame/assets/src/impl_fungibles.rs b/substrate/frame/assets/src/impl_fungibles.rs
index 4e85b20a1fb..25e18bfd437 100644
--- a/substrate/frame/assets/src/impl_fungibles.rs
+++ b/substrate/frame/assets/src/impl_fungibles.rs
@@ -147,3 +147,30 @@ impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T,
 		}
 	}
 }
+
+impl<T: Config<I>, I: 'static> fungibles::Create<T::AccountId> for Pallet<T, I> {
+	fn create(
+		id: T::AssetId,
+		admin: T::AccountId,
+		is_sufficient: bool,
+		min_balance: Self::Balance,
+	) -> DispatchResult {
+		Self::do_force_create(id, admin, is_sufficient, min_balance)
+	}
+}
+
+impl<T: Config<I>, I: 'static> fungibles::Destroy<T::AccountId> for Pallet<T, I> {
+	type DestroyWitness = DestroyWitness;
+
+	fn get_destroy_witness(asset: &T::AssetId) -> Option<Self::DestroyWitness> {
+		Asset::<T, I>::get(asset).map(|asset_details| asset_details.destroy_witness())
+	}
+
+	fn destroy(
+		id: T::AssetId,
+		witness: Self::DestroyWitness,
+		maybe_check_owner: Option<T::AccountId>,
+	) -> Result<Self::DestroyWitness, DispatchError> {
+		Self::do_destroy(id, witness, maybe_check_owner)
+	}
+}
diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs
index 797a3ae7ee9..2c9f994b3fe 100644
--- a/substrate/frame/assets/src/lib.rs
+++ b/substrate/frame/assets/src/lib.rs
@@ -143,6 +143,7 @@ use codec::HasCompact;
 use frame_support::{
 	dispatch::{DispatchError, DispatchResult},
 	ensure,
+	pallet_prelude::DispatchResultWithPostInfo,
 	traits::{
 		tokens::{fungibles, DepositConsequence, WithdrawConsequence},
 		BalanceStatus::Reserved,
@@ -437,29 +438,7 @@ pub mod pallet {
 		) -> DispatchResult {
 			T::ForceOrigin::ensure_origin(origin)?;
 			let owner = T::Lookup::lookup(owner)?;
-
-			ensure!(!Asset::<T, I>::contains_key(id), Error::<T, I>::InUse);
-			ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
-
-			Asset::<T, I>::insert(
-				id,
-				AssetDetails {
-					owner: owner.clone(),
-					issuer: owner.clone(),
-					admin: owner.clone(),
-					freezer: owner.clone(),
-					supply: Zero::zero(),
-					deposit: Zero::zero(),
-					min_balance,
-					is_sufficient,
-					accounts: 0,
-					sufficients: 0,
-					approvals: 0,
-					is_frozen: false,
-				},
-			);
-			Self::deposit_event(Event::ForceCreated(id, owner));
-			Ok(())
+			Self::do_force_create(id, owner, is_sufficient, min_balance)
 		}
 
 		/// Destroy a class of fungible assets.
@@ -494,39 +473,13 @@ pub mod pallet {
 				Ok(_) => None,
 				Err(origin) => Some(ensure_signed(origin)?),
 			};
-			Asset::<T, I>::try_mutate_exists(id, |maybe_details| {
-				let mut details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
-				if let Some(check_owner) = maybe_check_owner {
-					ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
-				}
-				ensure!(details.accounts <= witness.accounts, Error::<T, I>::BadWitness);
-				ensure!(details.sufficients <= witness.sufficients, Error::<T, I>::BadWitness);
-				ensure!(details.approvals <= witness.approvals, Error::<T, I>::BadWitness);
-
-				for (who, v) in Account::<T, I>::drain_prefix(id) {
-					Self::dead_account(id, &who, &mut details, v.sufficient);
-				}
-				debug_assert_eq!(details.accounts, 0);
-				debug_assert_eq!(details.sufficients, 0);
-
-				let metadata = Metadata::<T, I>::take(&id);
-				T::Currency::unreserve(
-					&details.owner,
-					details.deposit.saturating_add(metadata.deposit),
-				);
-
-				for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((&id,)) {
-					T::Currency::unreserve(&owner, approval.deposit);
-				}
-				Self::deposit_event(Event::Destroyed(id));
-
-				Ok(Some(T::WeightInfo::destroy(
-					details.accounts.saturating_sub(details.sufficients),
-					details.sufficients,
-					details.approvals,
-				))
-				.into())
-			})
+			let details = Self::do_destroy(id, witness, maybe_check_owner)?;
+			Ok(Some(T::WeightInfo::destroy(
+				details.accounts.saturating_sub(details.sufficients),
+				details.sufficients,
+				details.approvals,
+			))
+			.into())
 		}
 
 		/// Mint assets of a particular class.
diff --git a/substrate/frame/support/src/traits/tokens/fungibles.rs b/substrate/frame/support/src/traits/tokens/fungibles.rs
index 3f5a1c75860..457ec4e8bf2 100644
--- a/substrate/frame/support/src/traits/tokens/fungibles.rs
+++ b/substrate/frame/support/src/traits/tokens/fungibles.rs
@@ -227,3 +227,39 @@ impl<AccountId, T: Balanced<AccountId> + MutateHold<AccountId>> BalancedHold<Acc
 		<Self as fungibles::Balanced<AccountId>>::slash(asset, who, actual)
 	}
 }
+
+/// Trait for providing the ability to create new fungible assets.
+pub trait Create<AccountId>: Inspect<AccountId> {
+	/// Create a new fungible asset.
+	fn create(
+		id: Self::AssetId,
+		admin: AccountId,
+		is_sufficient: bool,
+		min_balance: Self::Balance,
+	) -> DispatchResult;
+}
+
+/// Trait for providing the ability to destroy existing fungible assets.
+pub trait Destroy<AccountId>: Inspect<AccountId> {
+	/// The witness data needed to destroy an asset.
+	type DestroyWitness;
+
+	/// Provide the appropriate witness data needed to destroy an asset.
+	fn get_destroy_witness(id: &Self::AssetId) -> Option<Self::DestroyWitness>;
+
+	/// Destroy an existing fungible asset.
+	/// * `id`: The `AssetId` to be destroyed.
+	/// * `witness`: Any witness data that needs to be provided to complete the operation
+	///   successfully.
+	/// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy
+	///   command. If not provided, we will not do any authorization checks before destroying the
+	///   asset.
+	///
+	/// If successful, this function will return the actual witness data from the destroyed asset.
+	/// This may be different than the witness data provided, and can be used to refund weight.
+	fn destroy(
+		id: Self::AssetId,
+		witness: Self::DestroyWitness,
+		maybe_check_owner: Option<AccountId>,
+	) -> Result<Self::DestroyWitness, DispatchError>;
+}
diff --git a/substrate/frame/support/src/traits/tokens/nonfungibles.rs b/substrate/frame/support/src/traits/tokens/nonfungibles.rs
index 452ee2212d6..b5a14761064 100644
--- a/substrate/frame/support/src/traits/tokens/nonfungibles.rs
+++ b/substrate/frame/support/src/traits/tokens/nonfungibles.rs
@@ -27,7 +27,7 @@
 //! Implementations of these traits may be converted to implementations of corresponding
 //! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter.
 
-use crate::dispatch::DispatchResult;
+use crate::dispatch::{DispatchError, DispatchResult};
 use codec::{Decode, Encode};
 use sp_runtime::TokenError;
 use sp_std::prelude::*;
@@ -123,6 +123,31 @@ pub trait Create<AccountId>: Inspect<AccountId> {
 	fn create_class(class: &Self::ClassId, who: &AccountId, admin: &AccountId) -> DispatchResult;
 }
 
+/// Trait for providing the ability to destroy classes of nonfungible assets.
+pub trait Destroy<AccountId>: Inspect<AccountId> {
+	/// The witness data needed to destroy an asset.
+	type DestroyWitness;
+
+	/// Provide the appropriate witness data needed to destroy an asset.
+	fn get_destroy_witness(class: &Self::ClassId) -> Option<Self::DestroyWitness>;
+
+	/// Destroy an existing fungible asset.
+	/// * `class`: The `ClassId` to be destroyed.
+	/// * `witness`: Any witness data that needs to be provided to complete the operation
+	///   successfully.
+	/// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy
+	///   command. If not provided, we will not do any authorization checks before destroying the
+	///   asset.
+	///
+	/// If successful, this function will return the actual witness data from the destroyed asset.
+	/// This may be different than the witness data provided, and can be used to refund weight.
+	fn destroy(
+		class: Self::ClassId,
+		witness: Self::DestroyWitness,
+		maybe_check_owner: Option<AccountId>,
+	) -> Result<Self::DestroyWitness, DispatchError>;
+}
+
 /// Trait for providing an interface for multiple classes of NFT-like assets which may be minted,
 /// burned and/or have attributes set on them.
 pub trait Mutate<AccountId>: Inspect<AccountId> {
diff --git a/substrate/frame/uniques/src/functions.rs b/substrate/frame/uniques/src/functions.rs
index a878a4910f7..68acf7f1879 100644
--- a/substrate/frame/uniques/src/functions.rs
+++ b/substrate/frame/uniques/src/functions.rs
@@ -80,6 +80,41 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
 		Ok(())
 	}
 
+	pub(super) fn do_destroy_class(
+		class: T::ClassId,
+		witness: DestroyWitness,
+		maybe_check_owner: Option<T::AccountId>,
+	) -> Result<DestroyWitness, DispatchError> {
+		Class::<T, I>::try_mutate_exists(class, |maybe_details| {
+			let class_details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
+			if let Some(check_owner) = maybe_check_owner {
+				ensure!(class_details.owner == check_owner, Error::<T, I>::NoPermission);
+			}
+			ensure!(class_details.instances == witness.instances, Error::<T, I>::BadWitness);
+			ensure!(
+				class_details.instance_metadatas == witness.instance_metadatas,
+				Error::<T, I>::BadWitness
+			);
+			ensure!(class_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
+
+			for (instance, details) in Asset::<T, I>::drain_prefix(&class) {
+				Account::<T, I>::remove((&details.owner, &class, &instance));
+			}
+			InstanceMetadataOf::<T, I>::remove_prefix(&class, None);
+			ClassMetadataOf::<T, I>::remove(&class);
+			Attribute::<T, I>::remove_prefix((&class,), None);
+			T::Currency::unreserve(&class_details.owner, class_details.total_deposit);
+
+			Self::deposit_event(Event::Destroyed(class));
+
+			Ok(DestroyWitness {
+				instances: class_details.instances,
+				instance_metadatas: class_details.instance_metadatas,
+				attributes: class_details.attributes,
+			})
+		})
+	}
+
 	pub(super) fn do_mint(
 		class: T::ClassId,
 		instance: T::InstanceId,
diff --git a/substrate/frame/uniques/src/impl_nonfungibles.rs b/substrate/frame/uniques/src/impl_nonfungibles.rs
index c5d5c6089f8..e68d2d4deec 100644
--- a/substrate/frame/uniques/src/impl_nonfungibles.rs
+++ b/substrate/frame/uniques/src/impl_nonfungibles.rs
@@ -19,13 +19,10 @@
 
 use super::*;
 use frame_support::{
-	traits::{
-		tokens::nonfungibles::{Create, Inspect, InspectEnumerable, Mutate, Transfer},
-		Get,
-	},
+	traits::{tokens::nonfungibles::*, Get},
 	BoundedSlice,
 };
-use sp_runtime::DispatchResult;
+use sp_runtime::{DispatchError, DispatchResult};
 use sp_std::convert::TryFrom;
 
 impl<T: Config<I>, I: 'static> Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
@@ -106,6 +103,22 @@ impl<T: Config<I>, I: 'static> Create<<T as SystemConfig>::AccountId> for Pallet
 	}
 }
 
+impl<T: Config<I>, I: 'static> Destroy<<T as SystemConfig>::AccountId> for Pallet<T, I> {
+	type DestroyWitness = DestroyWitness;
+
+	fn get_destroy_witness(class: &Self::ClassId) -> Option<DestroyWitness> {
+		Class::<T, I>::get(class).map(|a| a.destroy_witness())
+	}
+
+	fn destroy(
+		class: Self::ClassId,
+		witness: Self::DestroyWitness,
+		maybe_check_owner: Option<T::AccountId>,
+	) -> Result<Self::DestroyWitness, DispatchError> {
+		Self::do_destroy_class(class, witness, maybe_check_owner)
+	}
+}
+
 impl<T: Config<I>, I: 'static> Mutate<<T as SystemConfig>::AccountId> for Pallet<T, I> {
 	fn mint_into(
 		class: &Self::ClassId,
diff --git a/substrate/frame/uniques/src/lib.rs b/substrate/frame/uniques/src/lib.rs
index 8c716694051..1bf220e4a78 100644
--- a/substrate/frame/uniques/src/lib.rs
+++ b/substrate/frame/uniques/src/lib.rs
@@ -381,37 +381,19 @@ pub mod pallet {
 			origin: OriginFor<T>,
 			#[pallet::compact] class: T::ClassId,
 			witness: DestroyWitness,
-		) -> DispatchResult {
+		) -> DispatchResultWithPostInfo {
 			let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
 				Ok(_) => None,
 				Err(origin) => Some(ensure_signed(origin)?),
 			};
-			Class::<T, I>::try_mutate_exists(class, |maybe_details| {
-				let class_details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
-				if let Some(check_owner) = maybe_check_owner {
-					ensure!(class_details.owner == check_owner, Error::<T, I>::NoPermission);
-				}
-				ensure!(class_details.instances == witness.instances, Error::<T, I>::BadWitness);
-				ensure!(
-					class_details.instance_metadatas == witness.instance_metadatas,
-					Error::<T, I>::BadWitness
-				);
-				ensure!(class_details.attributes == witness.attributes, Error::<T, I>::BadWitness);
-
-				for (instance, details) in Asset::<T, I>::drain_prefix(&class) {
-					Account::<T, I>::remove((&details.owner, &class, &instance));
-				}
-				InstanceMetadataOf::<T, I>::remove_prefix(&class, None);
-				ClassMetadataOf::<T, I>::remove(&class);
-				Attribute::<T, I>::remove_prefix((&class,), None);
-				T::Currency::unreserve(&class_details.owner, class_details.total_deposit);
-
-				Self::deposit_event(Event::Destroyed(class));
-
-				// NOTE: could use postinfo to reflect the actual number of
-				// accounts/sufficient/approvals
-				Ok(())
-			})
+			let details = Self::do_destroy_class(class, witness, maybe_check_owner)?;
+
+			Ok(Some(T::WeightInfo::destroy(
+				details.instances,
+				details.instance_metadatas,
+				details.attributes,
+			))
+			.into())
 		}
 
 		/// Mint an asset instance of a particular class.
-- 
GitLab