Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Fungibles and Non-Fungibles Create and Destroy Traits + Assets and Uniques Implementation #9844

Merged
7 commits merged into from
Sep 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions frame/assets/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
emostov marked this conversation as resolved.
Show resolved Hide resolved
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,)) {
emostov marked this conversation as resolved.
Show resolved Hide resolved
T::Currency::unreserve(&owner, approval.deposit);
}
Self::deposit_event(Event::Destroyed(id));

Ok(DestroyWitness {
accounts: details.accounts,
sufficients: details.sufficients,
approvals: details.approvals,
})
})
}
}
27 changes: 27 additions & 0 deletions frame/assets/src/impl_fungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
65 changes: 9 additions & 56 deletions frame/assets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ use codec::HasCompact;
use frame_support::{
dispatch::{DispatchError, DispatchResult},
ensure,
pallet_prelude::DispatchResultWithPostInfo,
traits::{
tokens::{fungibles, DepositConsequence, WithdrawConsequence},
BalanceStatus::Reserved,
Expand Down Expand Up @@ -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(
Comment on lines -441 to -444
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just copied this logic into do_force_create

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.
Expand Down Expand Up @@ -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 {
Comment on lines -497 to -499
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just copied this logic directly into do_destroy

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.
Expand Down
36 changes: 36 additions & 0 deletions frame/support/src/traits/tokens/fungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Comment on lines +260 to +263
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: add an auto-implemented destroy function without the witness parameter (and maybe rename this to destroy_with_witness)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think that would be a dangerous API. Destroy can have varying complexity based on the number of assets it contains. The user should always investigate the witness information before executing the operation. If they truly with to be oblivious of this check, they can always provide a witness with max values, but I don't think we should encourage it.

) -> Result<Self::DestroyWitness, DispatchError>;
}
27 changes: 26 additions & 1 deletion frame/support/src/traits/tokens/nonfungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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> {
Expand Down
35 changes: 35 additions & 0 deletions frame/uniques/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 18 additions & 5 deletions frame/uniques/src/impl_nonfungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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,
Expand Down
Loading