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