Skip to content

Commit

Permalink
Fungibles and Non-Fungibles Create and Destroy Traits + Assets and Un…
Browse files Browse the repository at this point in the history
…iques Implementation (paritytech#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
  • Loading branch information
shawntabrizi authored and green-jay committed Oct 5, 2021
1 parent 58f0223 commit c23ba88
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 88 deletions.
84 changes: 84 additions & 0 deletions frame/assets/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,4 +477,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,
})
})
}
}
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 @@ -142,6 +142,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 @@ -436,29 +437,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.
Expand Down Expand Up @@ -493,39 +472,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.
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>,
) -> 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

0 comments on commit c23ba88

Please sign in to comment.