diff --git a/substrate/frame/assets/src/benchmarking.rs b/substrate/frame/assets/src/benchmarking.rs index 90b6f65b39..986eedfb6a 100644 --- a/substrate/frame/assets/src/benchmarking.rs +++ b/substrate/frame/assets/src/benchmarking.rs @@ -21,6 +21,7 @@ use super::*; use sp_runtime::traits::Bounded; use frame_system::RawOrigin as SystemOrigin; use frame_benchmarking::{benchmarks, account, whitelisted_caller}; +use frame_support::traits::Get; use crate::Module as Assets; @@ -79,7 +80,7 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, 1, 1u32.into()) verify { - assert_last_event::(RawEvent::Created(Default::default(), caller.clone(), caller).into()); + assert_last_event::(Event::Created(Default::default(), caller.clone(), caller).into()); } force_create { @@ -87,7 +88,7 @@ benchmarks! { let caller_lookup = T::Lookup::unlookup(caller.clone()); }: _(SystemOrigin::Root, Default::default(), caller_lookup, 1, 1u32.into()) verify { - assert_last_event::(RawEvent::ForceCreated(Default::default(), caller).into()); + assert_last_event::(Event::ForceCreated(Default::default(), caller).into()); } destroy { @@ -96,7 +97,7 @@ benchmarks! { add_zombies::(caller.clone(), z); }: _(SystemOrigin::Signed(caller), Default::default(), 10_000) verify { - assert_last_event::(RawEvent::Destroyed(Default::default()).into()); + assert_last_event::(Event::Destroyed(Default::default()).into()); } force_destroy { @@ -105,7 +106,7 @@ benchmarks! { add_zombies::(caller.clone(), z); }: _(SystemOrigin::Root, Default::default(), 10_000) verify { - assert_last_event::(RawEvent::Destroyed(Default::default()).into()); + assert_last_event::(Event::Destroyed(Default::default()).into()); } mint { @@ -113,7 +114,7 @@ benchmarks! { let amount = T::Balance::from(100u32); }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) verify { - assert_last_event::(RawEvent::Issued(Default::default(), caller, amount).into()); + assert_last_event::(Event::Issued(Default::default(), caller, amount).into()); } burn { @@ -121,7 +122,7 @@ benchmarks! { let (caller, caller_lookup) = create_default_minted_asset::(10, amount); }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, amount) verify { - assert_last_event::(RawEvent::Burned(Default::default(), caller, amount).into()); + assert_last_event::(Event::Burned(Default::default(), caller, amount).into()); } transfer { @@ -131,7 +132,7 @@ benchmarks! { let target_lookup = T::Lookup::unlookup(target.clone()); }: _(SystemOrigin::Signed(caller.clone()), Default::default(), target_lookup, amount) verify { - assert_last_event::(RawEvent::Transferred(Default::default(), caller, target, amount).into()); + assert_last_event::(Event::Transferred(Default::default(), caller, target, amount).into()); } force_transfer { @@ -141,14 +142,16 @@ benchmarks! { let target_lookup = T::Lookup::unlookup(target.clone()); }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup, target_lookup, amount) verify { - assert_last_event::(RawEvent::ForceTransferred(Default::default(), caller, target, amount).into()); + assert_last_event::( + Event::ForceTransferred(Default::default(), caller, target, amount).into() + ); } freeze { let (caller, caller_lookup) = create_default_minted_asset::(10, 100u32.into()); }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) verify { - assert_last_event::(RawEvent::Frozen(Default::default(), caller).into()); + assert_last_event::(Event::Frozen(Default::default(), caller).into()); } thaw { @@ -160,14 +163,14 @@ benchmarks! { )?; }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) verify { - assert_last_event::(RawEvent::Thawed(Default::default(), caller).into()); + assert_last_event::(Event::Thawed(Default::default(), caller).into()); } freeze_asset { let (caller, caller_lookup) = create_default_minted_asset::(10, 100u32.into()); }: _(SystemOrigin::Signed(caller.clone()), Default::default()) verify { - assert_last_event::(RawEvent::AssetFrozen(Default::default()).into()); + assert_last_event::(Event::AssetFrozen(Default::default()).into()); } thaw_asset { @@ -178,7 +181,7 @@ benchmarks! { )?; }: _(SystemOrigin::Signed(caller.clone()), Default::default()) verify { - assert_last_event::(RawEvent::AssetThawed(Default::default()).into()); + assert_last_event::(Event::AssetThawed(Default::default()).into()); } transfer_ownership { @@ -187,7 +190,7 @@ benchmarks! { let target_lookup = T::Lookup::unlookup(target.clone()); }: _(SystemOrigin::Signed(caller), Default::default(), target_lookup) verify { - assert_last_event::(RawEvent::OwnerChanged(Default::default(), target).into()); + assert_last_event::(Event::OwnerChanged(Default::default(), target).into()); } set_team { @@ -197,7 +200,7 @@ benchmarks! { let target2 = T::Lookup::unlookup(account("target", 2, SEED)); }: _(SystemOrigin::Signed(caller), Default::default(), target0.clone(), target1.clone(), target2.clone()) verify { - assert_last_event::(RawEvent::TeamChanged( + assert_last_event::(Event::TeamChanged( Default::default(), account("target", 0, SEED), account("target", 1, SEED), @@ -211,7 +214,7 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); }: _(SystemOrigin::Signed(caller), Default::default(), max_zombies) verify { - assert_last_event::(RawEvent::MaxZombiesChanged(Default::default(), max_zombies).into()); + assert_last_event::(Event::MaxZombiesChanged(Default::default(), max_zombies).into()); } set_metadata { @@ -226,7 +229,7 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); }: _(SystemOrigin::Signed(caller), Default::default(), name.clone(), symbol.clone(), decimals) verify { - assert_last_event::(RawEvent::MetadataSet(Default::default(), name, symbol, decimals).into()); + assert_last_event::(Event::MetadataSet(Default::default(), name, symbol, decimals).into()); } } diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 099361eceb..e5fa5f1fa5 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -29,9 +29,9 @@ //! * Asset Freezing //! * Asset Destruction (Burning) //! -//! To use it in your runtime, you need to implement the assets [`Config`](./trait.Config.html). +//! To use it in your runtime, you need to implement the assets [`Config`]. //! -//! The supported dispatchable functions are documented in the [`Call`](./enum.Call.html) enum. +//! The supported dispatchable functions are documented in the [`Call`] enum. //! //! ### Terminology //! @@ -114,55 +114,905 @@ mod benchmarking; pub mod weights; use sp_std::{fmt::Debug, prelude::*}; -use sp_runtime::{RuntimeDebug, traits::{ - Member, AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd -}}; -use codec::{Encode, Decode, HasCompact}; -use frame_support::{Parameter, decl_module, decl_event, decl_storage, decl_error, ensure, - traits::{Currency, ReservableCurrency, EnsureOrigin, Get, BalanceStatus::Reserved}, - dispatch::{DispatchResult, DispatchError}, +use sp_runtime::{ + RuntimeDebug, + traits::{ + AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd, + } +}; +use codec::{Encode, Decode, HasCompact}; +use frame_support::{ + ensure, + traits::{Currency, ReservableCurrency, BalanceStatus::Reserved}, + dispatch::DispatchError, }; -use frame_system::ensure_signed; pub use weights::WeightInfo; +pub use pallet::*; + type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -/// The module configuration trait. -pub trait Config: frame_system::Config { - /// The overarching event type. - type Event: From> + Into<::Event>; +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ + dispatch::DispatchResultWithPostInfo, + pallet_prelude::*, + }; + use frame_system::pallet_prelude::*; + use super::*; - /// The units in which we record balances. - type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); - /// The arithmetic type of asset identifier. - type AssetId: Member + Parameter + Default + Copy + HasCompact; + #[pallet::config] + /// The module configuration trait. + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; - /// The currency mechanism. - type Currency: ReservableCurrency; + /// The units in which we record balances. + type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy; - /// The origin which may forcibly create or destroy an asset. - type ForceOrigin: EnsureOrigin; + /// The arithmetic type of asset identifier. + type AssetId: Member + Parameter + Default + Copy + HasCompact; - /// The basic amount of funds that must be reserved when creating a new asset class. - type AssetDepositBase: Get>; + /// The currency mechanism. + type Currency: ReservableCurrency; - /// The additional funds that must be reserved for every zombie account that an asset class - /// supports. - type AssetDepositPerZombie: Get>; + /// The origin which may forcibly create or destroy an asset. + type ForceOrigin: EnsureOrigin; - /// The maximum length of a name or symbol stored on-chain. - type StringLimit: Get; + /// The basic amount of funds that must be reserved when creating a new asset class. + type AssetDepositBase: Get>; - /// The basic amount of funds that must be reserved when adding metadata to your asset. - type MetadataDepositBase: Get>; + /// The additional funds that must be reserved for every zombie account that an asset class + /// supports. + type AssetDepositPerZombie: Get>; - /// The additional funds that must be reserved for the number of bytes you store in your - /// metadata. - type MetadataDepositPerByte: Get>; + /// The maximum length of a name or symbol stored on-chain. + type StringLimit: Get; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; + /// The basic amount of funds that must be reserved when adding metadata to your asset. + type MetadataDepositBase: Get>; + + /// The additional funds that must be reserved for the number of bytes you store in your + /// metadata. + type MetadataDepositPerByte: Get>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Issue a new class of fungible assets from a public origin. + /// + /// This new asset class has no assets initially. + /// + /// The origin must be Signed and the sender must have sufficient funds free. + /// + /// Funds of sender are reserved according to the formula: + /// `AssetDepositBase + AssetDepositPerZombie * max_zombies`. + /// + /// Parameters: + /// - `id`: The identifier of the new asset. This must not be currently in use to identify + /// an existing asset. + /// - `owner`: The owner of this class of assets. The owner has full superuser permissions + /// over this asset, but may later change and configure the permissions using `transfer_ownership` + /// and `set_team`. + /// - `max_zombies`: The total number of accounts which may hold assets in this class yet + /// have no existential deposit. + /// - `min_balance`: The minimum balance of this new asset that any single account must + /// have. If an account's balance is reduced below this, then it collapses to zero. + /// + /// Emits `Created` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::create())] + pub(super) fn create( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + admin: ::Source, + max_zombies: u32, + min_balance: T::Balance, + ) -> DispatchResultWithPostInfo { + let owner = ensure_signed(origin)?; + let admin = T::Lookup::lookup(admin)?; + + ensure!(!Asset::::contains_key(id), Error::::InUse); + ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); + + let deposit = T::AssetDepositPerZombie::get() + .saturating_mul(max_zombies.into()) + .saturating_add(T::AssetDepositBase::get()); + T::Currency::reserve(&owner, deposit)?; + + Asset::::insert(id, AssetDetails { + owner: owner.clone(), + issuer: admin.clone(), + admin: admin.clone(), + freezer: admin.clone(), + supply: Zero::zero(), + deposit, + max_zombies, + min_balance, + zombies: Zero::zero(), + accounts: Zero::zero(), + is_frozen: false, + }); + Self::deposit_event(Event::Created(id, owner, admin)); + Ok(().into()) + } + + /// Issue a new class of fungible assets from a privileged origin. + /// + /// This new asset class has no assets initially. + /// + /// The origin must conform to `ForceOrigin`. + /// + /// Unlike `create`, no funds are reserved. + /// + /// - `id`: The identifier of the new asset. This must not be currently in use to identify + /// an existing asset. + /// - `owner`: The owner of this class of assets. The owner has full superuser permissions + /// over this asset, but may later change and configure the permissions using `transfer_ownership` + /// and `set_team`. + /// - `max_zombies`: The total number of accounts which may hold assets in this class yet + /// have no existential deposit. + /// - `min_balance`: The minimum balance of this new asset that any single account must + /// have. If an account's balance is reduced below this, then it collapses to zero. + /// + /// Emits `ForceCreated` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_create())] + pub(super) fn force_create( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + owner: ::Source, + #[pallet::compact] max_zombies: u32, + #[pallet::compact] min_balance: T::Balance, + ) -> DispatchResultWithPostInfo { + T::ForceOrigin::ensure_origin(origin)?; + let owner = T::Lookup::lookup(owner)?; + + ensure!(!Asset::::contains_key(id), Error::::InUse); + ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); + + Asset::::insert(id, AssetDetails { + owner: owner.clone(), + issuer: owner.clone(), + admin: owner.clone(), + freezer: owner.clone(), + supply: Zero::zero(), + deposit: Zero::zero(), + max_zombies, + min_balance, + zombies: Zero::zero(), + accounts: Zero::zero(), + is_frozen: false, + }); + Self::deposit_event(Event::ForceCreated(id, owner)); + Ok(().into()) + } + + /// Destroy a class of fungible assets owned by the sender. + /// + /// The origin must be Signed and the sender must be the owner of the asset `id`. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Emits `Destroyed` event when successful. + /// + /// Weight: `O(z)` where `z` is the number of zombie accounts. + #[pallet::weight(T::WeightInfo::destroy(*zombies_witness))] + pub(super) fn destroy( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + #[pallet::compact] zombies_witness: u32, + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + Asset::::try_mutate_exists(id, |maybe_details| { + let details = maybe_details.take().ok_or(Error::::Unknown)?; + ensure!(details.owner == origin, Error::::NoPermission); + ensure!(details.accounts == details.zombies, Error::::RefsLeft); + ensure!(details.zombies <= zombies_witness, Error::::BadWitness); + + let metadata = Metadata::::take(&id); + T::Currency::unreserve(&details.owner, details.deposit.saturating_add(metadata.deposit)); + + *maybe_details = None; + Account::::remove_prefix(&id); + Self::deposit_event(Event::Destroyed(id)); + Ok(().into()) + }) + } + + /// Destroy a class of fungible assets. + /// + /// The origin must conform to `ForceOrigin`. + /// + /// - `id`: The identifier of the asset to be destroyed. This must identify an existing + /// asset. + /// + /// Emits `Destroyed` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::force_destroy(*zombies_witness))] + pub(super) fn force_destroy( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + #[pallet::compact] zombies_witness: u32, + ) -> DispatchResultWithPostInfo { + T::ForceOrigin::ensure_origin(origin)?; + + Asset::::try_mutate_exists(id, |maybe_details| { + let details = maybe_details.take().ok_or(Error::::Unknown)?; + ensure!(details.accounts == details.zombies, Error::::RefsLeft); + ensure!(details.zombies <= zombies_witness, Error::::BadWitness); + + let metadata = Metadata::::take(&id); + T::Currency::unreserve(&details.owner, details.deposit.saturating_add(metadata.deposit)); + + *maybe_details = None; + Account::::remove_prefix(&id); + Self::deposit_event(Event::Destroyed(id)); + Ok(().into()) + }) + } + + /// Mint assets of a particular class. + /// + /// The origin must be Signed and the sender must be the Issuer of the asset `id`. + /// + /// - `id`: The identifier of the asset to have some amount minted. + /// - `beneficiary`: The account to be credited with the minted assets. + /// - `amount`: The amount of the asset to be minted. + /// + /// Emits `Destroyed` event when successful. + /// + /// Weight: `O(1)` + /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. + #[pallet::weight(T::WeightInfo::mint())] + pub(super) fn mint( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + beneficiary: ::Source, + #[pallet::compact] amount: T::Balance + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + + ensure!(&origin == &details.issuer, Error::::NoPermission); + details.supply = details.supply.checked_add(&amount).ok_or(Error::::Overflow)?; + + Account::::try_mutate(id, &beneficiary, |t| -> DispatchResultWithPostInfo { + let new_balance = t.balance.saturating_add(amount); + ensure!(new_balance >= details.min_balance, Error::::BalanceLow); + if t.balance.is_zero() { + t.is_zombie = Self::new_account(&beneficiary, details)?; + } + t.balance = new_balance; + Ok(().into()) + })?; + Self::deposit_event(Event::Issued(id, beneficiary, amount)); + Ok(().into()) + }) + } + + /// Reduce the balance of `who` by as much as possible up to `amount` assets of `id`. + /// + /// Origin must be Signed and the sender should be the Manager of the asset `id`. + /// + /// Bails with `BalanceZero` if the `who` is already dead. + /// + /// - `id`: The identifier of the asset to have some amount burned. + /// - `who`: The account to be debited from. + /// - `amount`: The maximum amount by which `who`'s balance should be reduced. + /// + /// Emits `Burned` with the actual amount burned. If this takes the balance to below the + /// minimum for the asset, then the amount burned is increased to take it to zero. + /// + /// Weight: `O(1)` + /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. + #[pallet::weight(T::WeightInfo::burn())] + pub(super) fn burn( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + who: ::Source, + #[pallet::compact] amount: T::Balance + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; + + Asset::::try_mutate(id, |maybe_details| { + let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &d.admin, Error::::NoPermission); + + let burned = Account::::try_mutate_exists( + id, + &who, + |maybe_account| -> Result { + let mut account = maybe_account.take().ok_or(Error::::BalanceZero)?; + let mut burned = amount.min(account.balance); + account.balance -= burned; + *maybe_account = if account.balance < d.min_balance { + burned += account.balance; + Self::dead_account(&who, d, account.is_zombie); + None + } else { + Some(account) + }; + Ok(burned) + } + )?; + + d.supply = d.supply.saturating_sub(burned); + + Self::deposit_event(Event::Burned(id, who, burned)); + Ok(().into()) + }) + } + + /// Move some assets from the sender account to another. + /// + /// Origin must be Signed. + /// + /// - `id`: The identifier of the asset to have some amount transferred. + /// - `target`: The account to be credited. + /// - `amount`: The amount by which the sender's balance of assets should be reduced and + /// `target`'s balance increased. The amount actually transferred may be slightly greater in + /// the case that the transfer would otherwise take the sender balance above zero but below + /// the minimum balance. Must be greater than zero. + /// + /// Emits `Transferred` with the actual amount transferred. If this takes the source balance + /// to below the minimum for the asset, then the amount transferred is increased to take it + /// to zero. + /// + /// Weight: `O(1)` + /// Modes: Pre-existence of `target`; Post-existence of sender; Prior & post zombie-status + /// of sender; Account pre-existence of `target`. + #[pallet::weight(T::WeightInfo::transfer())] + pub(super) fn transfer( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + target: ::Source, + #[pallet::compact] amount: T::Balance + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + ensure!(!amount.is_zero(), Error::::AmountZero); + + let mut origin_account = Account::::get(id, &origin); + ensure!(!origin_account.is_frozen, Error::::Frozen); + origin_account.balance = origin_account.balance.checked_sub(&amount) + .ok_or(Error::::BalanceLow)?; + + let dest = T::Lookup::lookup(target)?; + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(!details.is_frozen, Error::::Frozen); + + if dest == origin { + return Ok(().into()) + } + + let mut amount = amount; + if origin_account.balance < details.min_balance { + amount += origin_account.balance; + origin_account.balance = Zero::zero(); + } + + Account::::try_mutate(id, &dest, |a| -> DispatchResultWithPostInfo { + let new_balance = a.balance.saturating_add(amount); + ensure!(new_balance >= details.min_balance, Error::::BalanceLow); + if a.balance.is_zero() { + a.is_zombie = Self::new_account(&dest, details)?; + } + a.balance = new_balance; + Ok(().into()) + })?; + + match origin_account.balance.is_zero() { + false => { + Self::dezombify(&origin, details, &mut origin_account.is_zombie); + Account::::insert(id, &origin, &origin_account) + } + true => { + Self::dead_account(&origin, details, origin_account.is_zombie); + Account::::remove(id, &origin); + } + } + + Self::deposit_event(Event::Transferred(id, origin, dest, amount)); + Ok(().into()) + }) + } + + /// Move some assets from one account to another. + /// + /// Origin must be Signed and the sender should be the Admin of the asset `id`. + /// + /// - `id`: The identifier of the asset to have some amount transferred. + /// - `source`: The account to be debited. + /// - `dest`: The account to be credited. + /// - `amount`: The amount by which the `source`'s balance of assets should be reduced and + /// `dest`'s balance increased. The amount actually transferred may be slightly greater in + /// the case that the transfer would otherwise take the `source` balance above zero but + /// below the minimum balance. Must be greater than zero. + /// + /// Emits `Transferred` with the actual amount transferred. If this takes the source balance + /// to below the minimum for the asset, then the amount transferred is increased to take it + /// to zero. + /// + /// Weight: `O(1)` + /// Modes: Pre-existence of `dest`; Post-existence of `source`; Prior & post zombie-status + /// of `source`; Account pre-existence of `dest`. + #[pallet::weight(T::WeightInfo::force_transfer())] + pub(super) fn force_transfer( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + source: ::Source, + dest: ::Source, + #[pallet::compact] amount: T::Balance, + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + let source = T::Lookup::lookup(source)?; + let mut source_account = Account::::get(id, &source); + let mut amount = amount.min(source_account.balance); + ensure!(!amount.is_zero(), Error::::AmountZero); + + let dest = T::Lookup::lookup(dest)?; + if dest == source { + return Ok(().into()) + } + + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &details.admin, Error::::NoPermission); + + source_account.balance -= amount; + if source_account.balance < details.min_balance { + amount += source_account.balance; + source_account.balance = Zero::zero(); + } + + Account::::try_mutate(id, &dest, |a| -> DispatchResultWithPostInfo { + let new_balance = a.balance.saturating_add(amount); + ensure!(new_balance >= details.min_balance, Error::::BalanceLow); + if a.balance.is_zero() { + a.is_zombie = Self::new_account(&dest, details)?; + } + a.balance = new_balance; + Ok(().into()) + })?; + + match source_account.balance.is_zero() { + false => { + Self::dezombify(&source, details, &mut source_account.is_zombie); + Account::::insert(id, &source, &source_account) + } + true => { + Self::dead_account(&source, details, source_account.is_zombie); + Account::::remove(id, &source); + } + } + + Self::deposit_event(Event::ForceTransferred(id, source, dest, amount)); + Ok(().into()) + }) + } + + /// Disallow further unprivileged transfers from an account. + /// + /// Origin must be Signed and the sender should be the Freezer of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// - `who`: The account to be frozen. + /// + /// Emits `Frozen`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::freeze())] + pub(super) fn freeze( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + who: ::Source + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &d.freezer, Error::::NoPermission); + let who = T::Lookup::lookup(who)?; + ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); + + Account::::mutate(id, &who, |a| a.is_frozen = true); + + Self::deposit_event(Event::::Frozen(id, who)); + Ok(().into()) + } + + /// Allow unprivileged transfers from an account again. + /// + /// Origin must be Signed and the sender should be the Admin of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// - `who`: The account to be unfrozen. + /// + /// Emits `Thawed`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::thaw())] + pub(super) fn thaw( + origin: OriginFor, + #[pallet::compact] + id: T::AssetId, + who: ::Source + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + let details = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &details.admin, Error::::NoPermission); + let who = T::Lookup::lookup(who)?; + ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); + + Account::::mutate(id, &who, |a| a.is_frozen = false); + + Self::deposit_event(Event::::Thawed(id, who)); + Ok(().into()) + } + + /// Disallow further unprivileged transfers for the asset class. + /// + /// Origin must be Signed and the sender should be the Freezer of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// + /// Emits `Frozen`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::freeze_asset())] + pub(super) fn freeze_asset( + origin: OriginFor, + #[pallet::compact] id: T::AssetId + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + Asset::::try_mutate(id, |maybe_details| { + let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &d.freezer, Error::::NoPermission); + + d.is_frozen = true; + + Self::deposit_event(Event::::AssetFrozen(id)); + Ok(().into()) + }) + } + + /// Allow unprivileged transfers for the asset again. + /// + /// Origin must be Signed and the sender should be the Admin of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// + /// Emits `Thawed`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::thaw_asset())] + pub(super) fn thaw_asset( + origin: OriginFor, + #[pallet::compact] id: T::AssetId + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + Asset::::try_mutate(id, |maybe_details| { + let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &d.admin, Error::::NoPermission); + + d.is_frozen = false; + + Self::deposit_event(Event::::AssetThawed(id)); + Ok(().into()) + }) + } + + /// Change the Owner of an asset. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// - `owner`: The new Owner of this asset. + /// + /// Emits `OwnerChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::transfer_ownership())] + pub(super) fn transfer_ownership( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + owner: ::Source, + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + let owner = T::Lookup::lookup(owner)?; + + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &details.owner, Error::::NoPermission); + if details.owner == owner { return Ok(().into()) } + + // Move the deposit to the new owner. + T::Currency::repatriate_reserved(&details.owner, &owner, details.deposit, Reserved)?; + + details.owner = owner.clone(); + + Self::deposit_event(Event::OwnerChanged(id, owner)); + Ok(().into()) + }) + } + + /// Change the Issuer, Admin and Freezer of an asset. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// - `id`: The identifier of the asset to be frozen. + /// - `issuer`: The new Issuer of this asset. + /// - `admin`: The new Admin of this asset. + /// - `freezer`: The new Freezer of this asset. + /// + /// Emits `TeamChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_team())] + pub(super) fn set_team( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + issuer: ::Source, + admin: ::Source, + freezer: ::Source, + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + let issuer = T::Lookup::lookup(issuer)?; + let admin = T::Lookup::lookup(admin)?; + let freezer = T::Lookup::lookup(freezer)?; + + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &details.owner, Error::::NoPermission); + + details.issuer = issuer.clone(); + details.admin = admin.clone(); + details.freezer = freezer.clone(); + + Self::deposit_event(Event::TeamChanged(id, issuer, admin, freezer)); + Ok(().into()) + }) + } + + /// Set the maximum number of zombie accounts for an asset. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// Funds of sender are reserved according to the formula: + /// `AssetDepositBase + AssetDepositPerZombie * max_zombies` taking into account + /// any already reserved funds. + /// + /// - `id`: The identifier of the asset to update zombie count. + /// - `max_zombies`: The new number of zombies allowed for this asset. + /// + /// Emits `MaxZombiesChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_max_zombies())] + pub(super) fn set_max_zombies( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + #[pallet::compact] max_zombies: u32, + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + Asset::::try_mutate(id, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + ensure!(&origin == &details.owner, Error::::NoPermission); + ensure!(max_zombies >= details.zombies, Error::::TooManyZombies); + + let new_deposit = T::AssetDepositPerZombie::get() + .saturating_mul(max_zombies.into()) + .saturating_add(T::AssetDepositBase::get()); + + if new_deposit > details.deposit { + T::Currency::reserve(&origin, new_deposit - details.deposit)?; + } else { + T::Currency::unreserve(&origin, details.deposit - new_deposit); + } + + details.max_zombies = max_zombies; + + Self::deposit_event(Event::MaxZombiesChanged(id, max_zombies)); + Ok(().into()) + }) + } + + /// Set the metadata for an asset. + /// + /// NOTE: There is no `unset_metadata` call. Simply pass an empty name, symbol, + /// and 0 decimals to this function to remove the metadata of an asset and + /// return your deposit. + /// + /// Origin must be Signed and the sender should be the Owner of the asset `id`. + /// + /// Funds of sender are reserved according to the formula: + /// `MetadataDepositBase + MetadataDepositPerByte * (name.len + symbol.len)` taking into + /// account any already reserved funds. + /// + /// - `id`: The identifier of the asset to update. + /// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`. + /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. + /// - `decimals`: The number of decimals this asset uses to represent one unit. + /// + /// Emits `MaxZombiesChanged`. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))] + pub(super) fn set_metadata( + origin: OriginFor, + #[pallet::compact] id: T::AssetId, + name: Vec, + symbol: Vec, + decimals: u8, + ) -> DispatchResultWithPostInfo { + let origin = ensure_signed(origin)?; + + ensure!(name.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); + ensure!(symbol.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); + + let d = Asset::::get(id).ok_or(Error::::Unknown)?; + ensure!(&origin == &d.owner, Error::::NoPermission); + + Metadata::::try_mutate_exists(id, |metadata| { + let bytes_used = name.len() + symbol.len(); + let old_deposit = match metadata { + Some(m) => m.deposit, + None => Default::default() + }; + + // Metadata is being removed + if bytes_used.is_zero() && decimals.is_zero() { + T::Currency::unreserve(&origin, old_deposit); + *metadata = None; + } else { + let new_deposit = T::MetadataDepositPerByte::get() + .saturating_mul(((name.len() + symbol.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()); + + if new_deposit > old_deposit { + T::Currency::reserve(&origin, new_deposit - old_deposit)?; + } else { + T::Currency::unreserve(&origin, old_deposit - new_deposit); + } + + *metadata = Some(AssetMetadata { + deposit: new_deposit, + name: name.clone(), + symbol: symbol.clone(), + decimals, + }) + } + + Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals)); + Ok(().into()) + }) + } + + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::metadata(T::AccountId = "AccountId", T::Balance = "Balance", T::AssetId = "AssetId")] + pub enum Event { + /// Some asset class was created. \[asset_id, creator, owner\] + Created(T::AssetId, T::AccountId, T::AccountId), + /// Some assets were issued. \[asset_id, owner, total_supply\] + Issued(T::AssetId, T::AccountId, T::Balance), + /// Some assets were transferred. \[asset_id, from, to, amount\] + Transferred(T::AssetId, T::AccountId, T::AccountId, T::Balance), + /// Some assets were destroyed. \[asset_id, owner, balance\] + Burned(T::AssetId, T::AccountId, T::Balance), + /// The management team changed \[asset_id, issuer, admin, freezer\] + TeamChanged(T::AssetId, T::AccountId, T::AccountId, T::AccountId), + /// The owner changed \[asset_id, owner\] + OwnerChanged(T::AssetId, T::AccountId), + /// Some assets was transferred by an admin. \[asset_id, from, to, amount\] + ForceTransferred(T::AssetId, T::AccountId, T::AccountId, T::Balance), + /// Some account `who` was frozen. \[asset_id, who\] + Frozen(T::AssetId, T::AccountId), + /// Some account `who` was thawed. \[asset_id, who\] + Thawed(T::AssetId, T::AccountId), + /// Some asset `asset_id` was frozen. \[asset_id\] + AssetFrozen(T::AssetId), + /// Some asset `asset_id` was thawed. \[asset_id\] + AssetThawed(T::AssetId), + /// An asset class was destroyed. + Destroyed(T::AssetId), + /// Some asset class was force-created. \[asset_id, owner\] + ForceCreated(T::AssetId, T::AccountId), + /// The maximum amount of zombies allowed has changed. \[asset_id, max_zombies\] + MaxZombiesChanged(T::AssetId, u32), + /// New metadata has been set for an asset. \[asset_id, name, symbol, decimals\] + MetadataSet(T::AssetId, Vec, Vec, u8), + } + + #[deprecated(note = "use `Event` instead")] + pub type RawEvent = Event; + + #[pallet::error] + pub enum Error { + /// Transfer amount should be non-zero. + AmountZero, + /// Account balance must be greater than or equal to the transfer amount. + BalanceLow, + /// Balance should be non-zero. + BalanceZero, + /// The signing account has no permission to do the operation. + NoPermission, + /// The given asset ID is unknown. + Unknown, + /// The origin account is frozen. + Frozen, + /// The asset ID is already taken. + InUse, + /// Too many zombie accounts in use. + TooManyZombies, + /// Attempt to destroy an asset class when non-zombie, reference-bearing accounts exist. + RefsLeft, + /// Invalid witness data given. + BadWitness, + /// Minimum balance should be non-zero. + MinBalanceZero, + /// A mint operation lead to an overflow. + Overflow, + /// Some internal state is broken. + BadState, + /// Invalid metadata given. + BadMetadata, + } + + #[pallet::storage] + /// Details of an asset. + pub(super) type Asset = StorageMap< + _, + Blake2_128Concat, + T::AssetId, + AssetDetails> + >; + #[pallet::storage] + /// The number of units of assets held by any given account. + pub(super) type Account = StorageDoubleMap< + _, + Blake2_128Concat, + T::AssetId, + Blake2_128Concat, + T::AccountId, + AssetBalance, + ValueQuery + >; + #[pallet::storage] + /// Metadata of an asset. + pub(super) type Metadata = StorageMap< + _, + Blake2_128Concat, + T::AssetId, + AssetMetadata>, + ValueQuery + >; } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)] @@ -224,800 +1074,8 @@ pub struct AssetMetadata { decimals: u8, } -decl_storage! { - trait Store for Module as Assets { - /// Details of an asset. - Asset: map hasher(blake2_128_concat) T::AssetId => Option, - >>; - - /// The number of units of assets held by any given account. - Account: double_map - hasher(blake2_128_concat) T::AssetId, - hasher(blake2_128_concat) T::AccountId - => AssetBalance; - - /// Metadata of an asset. - Metadata: map hasher(blake2_128_concat) T::AssetId => AssetMetadata>; - } -} - -decl_event! { - pub enum Event where - ::AccountId, - ::Balance, - ::AssetId, - { - /// Some asset class was created. \[asset_id, creator, owner\] - Created(AssetId, AccountId, AccountId), - /// Some assets were issued. \[asset_id, owner, total_supply\] - Issued(AssetId, AccountId, Balance), - /// Some assets were transferred. \[asset_id, from, to, amount\] - Transferred(AssetId, AccountId, AccountId, Balance), - /// Some assets were destroyed. \[asset_id, owner, balance\] - Burned(AssetId, AccountId, Balance), - /// The management team changed \[asset_id, issuer, admin, freezer\] - TeamChanged(AssetId, AccountId, AccountId, AccountId), - /// The owner changed \[asset_id, owner\] - OwnerChanged(AssetId, AccountId), - /// Some assets was transferred by an admin. \[asset_id, from, to, amount\] - ForceTransferred(AssetId, AccountId, AccountId, Balance), - /// Some account `who` was frozen. \[asset_id, who\] - Frozen(AssetId, AccountId), - /// Some account `who` was thawed. \[asset_id, who\] - Thawed(AssetId, AccountId), - /// Some asset `asset_id` was frozen. \[asset_id\] - AssetFrozen(AssetId), - /// Some asset `asset_id` was thawed. \[asset_id\] - AssetThawed(AssetId), - /// An asset class was destroyed. - Destroyed(AssetId), - /// Some asset class was force-created. \[asset_id, owner\] - ForceCreated(AssetId, AccountId), - /// The maximum amount of zombies allowed has changed. \[asset_id, max_zombies\] - MaxZombiesChanged(AssetId, u32), - /// New metadata has been set for an asset. \[asset_id, name, symbol, decimals\] - MetadataSet(AssetId, Vec, Vec, u8), - } -} - -decl_error! { - pub enum Error for Module { - /// Transfer amount should be non-zero. - AmountZero, - /// Account balance must be greater than or equal to the transfer amount. - BalanceLow, - /// Balance should be non-zero. - BalanceZero, - /// The signing account has no permission to do the operation. - NoPermission, - /// The given asset ID is unknown. - Unknown, - /// The origin account is frozen. - Frozen, - /// The asset ID is already taken. - InUse, - /// Too many zombie accounts in use. - TooManyZombies, - /// Attempt to destroy an asset class when non-zombie, reference-bearing accounts exist. - RefsLeft, - /// Invalid witness data given. - BadWitness, - /// Minimum balance should be non-zero. - MinBalanceZero, - /// A mint operation lead to an overflow. - Overflow, - /// Some internal state is broken. - BadState, - /// Invalid metadata given. - BadMetadata, - } -} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - type Error = Error; - - fn deposit_event() = default; - - /// Issue a new class of fungible assets from a public origin. - /// - /// This new asset class has no assets initially. - /// - /// The origin must be Signed and the sender must have sufficient funds free. - /// - /// Funds of sender are reserved according to the formula: - /// `AssetDepositBase + AssetDepositPerZombie * max_zombies`. - /// - /// Parameters: - /// - `id`: The identifier of the new asset. This must not be currently in use to identify - /// an existing asset. - /// - `owner`: The owner of this class of assets. The owner has full superuser permissions - /// over this asset, but may later change and configure the permissions using `transfer_ownership` - /// and `set_team`. - /// - `max_zombies`: The total number of accounts which may hold assets in this class yet - /// have no existential deposit. - /// - `min_balance`: The minimum balance of this new asset that any single account must - /// have. If an account's balance is reduced below this, then it collapses to zero. - /// - /// Emits `Created` event when successful. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::create()] - fn create(origin, - #[compact] id: T::AssetId, - admin: ::Source, - max_zombies: u32, - min_balance: T::Balance, - ) { - let owner = ensure_signed(origin)?; - let admin = T::Lookup::lookup(admin)?; - - ensure!(!Asset::::contains_key(id), Error::::InUse); - ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); - - let deposit = T::AssetDepositPerZombie::get() - .saturating_mul(max_zombies.into()) - .saturating_add(T::AssetDepositBase::get()); - T::Currency::reserve(&owner, deposit)?; - - Asset::::insert(id, AssetDetails { - owner: owner.clone(), - issuer: admin.clone(), - admin: admin.clone(), - freezer: admin.clone(), - supply: Zero::zero(), - deposit, - max_zombies, - min_balance, - zombies: Zero::zero(), - accounts: Zero::zero(), - is_frozen: false, - }); - Self::deposit_event(RawEvent::Created(id, owner, admin)); - } - - /// Issue a new class of fungible assets from a privileged origin. - /// - /// This new asset class has no assets initially. - /// - /// The origin must conform to `ForceOrigin`. - /// - /// Unlike `create`, no funds are reserved. - /// - /// - `id`: The identifier of the new asset. This must not be currently in use to identify - /// an existing asset. - /// - `owner`: The owner of this class of assets. The owner has full superuser permissions - /// over this asset, but may later change and configure the permissions using `transfer_ownership` - /// and `set_team`. - /// - `max_zombies`: The total number of accounts which may hold assets in this class yet - /// have no existential deposit. - /// - `min_balance`: The minimum balance of this new asset that any single account must - /// have. If an account's balance is reduced below this, then it collapses to zero. - /// - /// Emits `ForceCreated` event when successful. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::force_create()] - fn force_create(origin, - #[compact] id: T::AssetId, - owner: ::Source, - #[compact] max_zombies: u32, - #[compact] min_balance: T::Balance, - ) { - T::ForceOrigin::ensure_origin(origin)?; - let owner = T::Lookup::lookup(owner)?; - - ensure!(!Asset::::contains_key(id), Error::::InUse); - ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); - - Asset::::insert(id, AssetDetails { - owner: owner.clone(), - issuer: owner.clone(), - admin: owner.clone(), - freezer: owner.clone(), - supply: Zero::zero(), - deposit: Zero::zero(), - max_zombies, - min_balance, - zombies: Zero::zero(), - accounts: Zero::zero(), - is_frozen: false, - }); - Self::deposit_event(RawEvent::ForceCreated(id, owner)); - } - - /// Destroy a class of fungible assets owned by the sender. - /// - /// The origin must be Signed and the sender must be the owner of the asset `id`. - /// - /// - `id`: The identifier of the asset to be destroyed. This must identify an existing - /// asset. - /// - /// Emits `Destroyed` event when successful. - /// - /// Weight: `O(z)` where `z` is the number of zombie accounts. - #[weight = T::WeightInfo::destroy(*zombies_witness)] - fn destroy(origin, - #[compact] id: T::AssetId, - #[compact] zombies_witness: u32, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Asset::::try_mutate_exists(id, |maybe_details| { - let details = maybe_details.take().ok_or(Error::::Unknown)?; - ensure!(details.owner == origin, Error::::NoPermission); - ensure!(details.accounts == details.zombies, Error::::RefsLeft); - ensure!(details.zombies <= zombies_witness, Error::::BadWitness); - - let metadata = Metadata::::take(&id); - T::Currency::unreserve(&details.owner, details.deposit.saturating_add(metadata.deposit)); - - *maybe_details = None; - Account::::remove_prefix(&id); - Self::deposit_event(RawEvent::Destroyed(id)); - Ok(()) - }) - } - - /// Destroy a class of fungible assets. - /// - /// The origin must conform to `ForceOrigin`. - /// - /// - `id`: The identifier of the asset to be destroyed. This must identify an existing - /// asset. - /// - /// Emits `Destroyed` event when successful. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::force_destroy(*zombies_witness)] - fn force_destroy(origin, - #[compact] id: T::AssetId, - #[compact] zombies_witness: u32, - ) -> DispatchResult { - T::ForceOrigin::ensure_origin(origin)?; - - Asset::::try_mutate_exists(id, |maybe_details| { - let details = maybe_details.take().ok_or(Error::::Unknown)?; - ensure!(details.accounts == details.zombies, Error::::RefsLeft); - ensure!(details.zombies <= zombies_witness, Error::::BadWitness); - - let metadata = Metadata::::take(&id); - T::Currency::unreserve(&details.owner, details.deposit.saturating_add(metadata.deposit)); - - *maybe_details = None; - Account::::remove_prefix(&id); - Self::deposit_event(RawEvent::Destroyed(id)); - Ok(()) - }) - } - - /// Mint assets of a particular class. - /// - /// The origin must be Signed and the sender must be the Issuer of the asset `id`. - /// - /// - `id`: The identifier of the asset to have some amount minted. - /// - `beneficiary`: The account to be credited with the minted assets. - /// - `amount`: The amount of the asset to be minted. - /// - /// Emits `Destroyed` event when successful. - /// - /// Weight: `O(1)` - /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. - #[weight = T::WeightInfo::mint()] - fn mint(origin, - #[compact] id: T::AssetId, - beneficiary: ::Source, - #[compact] amount: T::Balance - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let beneficiary = T::Lookup::lookup(beneficiary)?; - - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - - ensure!(&origin == &details.issuer, Error::::NoPermission); - details.supply = details.supply.checked_add(&amount).ok_or(Error::::Overflow)?; - - Account::::try_mutate(id, &beneficiary, |t| -> DispatchResult { - let new_balance = t.balance.saturating_add(amount); - ensure!(new_balance >= details.min_balance, Error::::BalanceLow); - if t.balance.is_zero() { - t.is_zombie = Self::new_account(&beneficiary, details)?; - } - t.balance = new_balance; - Ok(()) - })?; - Self::deposit_event(RawEvent::Issued(id, beneficiary, amount)); - Ok(()) - }) - } - - /// Reduce the balance of `who` by as much as possible up to `amount` assets of `id`. - /// - /// Origin must be Signed and the sender should be the Manager of the asset `id`. - /// - /// Bails with `BalanceZero` if the `who` is already dead. - /// - /// - `id`: The identifier of the asset to have some amount burned. - /// - `who`: The account to be debited from. - /// - `amount`: The maximum amount by which `who`'s balance should be reduced. - /// - /// Emits `Burned` with the actual amount burned. If this takes the balance to below the - /// minimum for the asset, then the amount burned is increased to take it to zero. - /// - /// Weight: `O(1)` - /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. - #[weight = T::WeightInfo::burn()] - fn burn(origin, - #[compact] id: T::AssetId, - who: ::Source, - #[compact] amount: T::Balance - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let who = T::Lookup::lookup(who)?; - - Asset::::try_mutate(id, |maybe_details| { - let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.admin, Error::::NoPermission); - - let burned = Account::::try_mutate_exists( - id, - &who, - |maybe_account| -> Result { - let mut account = maybe_account.take().ok_or(Error::::BalanceZero)?; - let mut burned = amount.min(account.balance); - account.balance -= burned; - *maybe_account = if account.balance < d.min_balance { - burned += account.balance; - Self::dead_account(&who, d, account.is_zombie); - None - } else { - Some(account) - }; - Ok(burned) - } - )?; - - d.supply = d.supply.saturating_sub(burned); - - Self::deposit_event(RawEvent::Burned(id, who, burned)); - Ok(()) - }) - } - - /// Move some assets from the sender account to another. - /// - /// Origin must be Signed. - /// - /// - `id`: The identifier of the asset to have some amount transferred. - /// - `target`: The account to be credited. - /// - `amount`: The amount by which the sender's balance of assets should be reduced and - /// `target`'s balance increased. The amount actually transferred may be slightly greater in - /// the case that the transfer would otherwise take the sender balance above zero but below - /// the minimum balance. Must be greater than zero. - /// - /// Emits `Transferred` with the actual amount transferred. If this takes the source balance - /// to below the minimum for the asset, then the amount transferred is increased to take it - /// to zero. - /// - /// Weight: `O(1)` - /// Modes: Pre-existence of `target`; Post-existence of sender; Prior & post zombie-status - /// of sender; Account pre-existence of `target`. - #[weight = T::WeightInfo::transfer()] - fn transfer(origin, - #[compact] id: T::AssetId, - target: ::Source, - #[compact] amount: T::Balance - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - ensure!(!amount.is_zero(), Error::::AmountZero); - - let mut origin_account = Account::::get(id, &origin); - ensure!(!origin_account.is_frozen, Error::::Frozen); - origin_account.balance = origin_account.balance.checked_sub(&amount) - .ok_or(Error::::BalanceLow)?; - - let dest = T::Lookup::lookup(target)?; - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(!details.is_frozen, Error::::Frozen); - - if dest == origin { - return Ok(()) - } - - let mut amount = amount; - if origin_account.balance < details.min_balance { - amount += origin_account.balance; - origin_account.balance = Zero::zero(); - } - - Account::::try_mutate(id, &dest, |a| -> DispatchResult { - let new_balance = a.balance.saturating_add(amount); - ensure!(new_balance >= details.min_balance, Error::::BalanceLow); - if a.balance.is_zero() { - a.is_zombie = Self::new_account(&dest, details)?; - } - a.balance = new_balance; - Ok(()) - })?; - - match origin_account.balance.is_zero() { - false => { - Self::dezombify(&origin, details, &mut origin_account.is_zombie); - Account::::insert(id, &origin, &origin_account) - } - true => { - Self::dead_account(&origin, details, origin_account.is_zombie); - Account::::remove(id, &origin); - } - } - - Self::deposit_event(RawEvent::Transferred(id, origin, dest, amount)); - Ok(()) - }) - } - - /// Move some assets from one account to another. - /// - /// Origin must be Signed and the sender should be the Admin of the asset `id`. - /// - /// - `id`: The identifier of the asset to have some amount transferred. - /// - `source`: The account to be debited. - /// - `dest`: The account to be credited. - /// - `amount`: The amount by which the `source`'s balance of assets should be reduced and - /// `dest`'s balance increased. The amount actually transferred may be slightly greater in - /// the case that the transfer would otherwise take the `source` balance above zero but - /// below the minimum balance. Must be greater than zero. - /// - /// Emits `Transferred` with the actual amount transferred. If this takes the source balance - /// to below the minimum for the asset, then the amount transferred is increased to take it - /// to zero. - /// - /// Weight: `O(1)` - /// Modes: Pre-existence of `dest`; Post-existence of `source`; Prior & post zombie-status - /// of `source`; Account pre-existence of `dest`. - #[weight = T::WeightInfo::force_transfer()] - fn force_transfer(origin, - #[compact] id: T::AssetId, - source: ::Source, - dest: ::Source, - #[compact] amount: T::Balance, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - let source = T::Lookup::lookup(source)?; - let mut source_account = Account::::get(id, &source); - let mut amount = amount.min(source_account.balance); - ensure!(!amount.is_zero(), Error::::AmountZero); - - let dest = T::Lookup::lookup(dest)?; - if dest == source { - return Ok(()) - } - - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.admin, Error::::NoPermission); - - source_account.balance -= amount; - if source_account.balance < details.min_balance { - amount += source_account.balance; - source_account.balance = Zero::zero(); - } - - Account::::try_mutate(id, &dest, |a| -> DispatchResult { - let new_balance = a.balance.saturating_add(amount); - ensure!(new_balance >= details.min_balance, Error::::BalanceLow); - if a.balance.is_zero() { - a.is_zombie = Self::new_account(&dest, details)?; - } - a.balance = new_balance; - Ok(()) - })?; - - match source_account.balance.is_zero() { - false => { - Self::dezombify(&source, details, &mut source_account.is_zombie); - Account::::insert(id, &source, &source_account) - } - true => { - Self::dead_account(&source, details, source_account.is_zombie); - Account::::remove(id, &source); - } - } - - Self::deposit_event(RawEvent::ForceTransferred(id, source, dest, amount)); - Ok(()) - }) - } - - /// Disallow further unprivileged transfers from an account. - /// - /// Origin must be Signed and the sender should be the Freezer of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - `who`: The account to be frozen. - /// - /// Emits `Frozen`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::freeze()] - fn freeze(origin, #[compact] id: T::AssetId, who: ::Source) { - let origin = ensure_signed(origin)?; - - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); - let who = T::Lookup::lookup(who)?; - ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); - - Account::::mutate(id, &who, |a| a.is_frozen = true); - - Self::deposit_event(Event::::Frozen(id, who)); - } - - /// Allow unprivileged transfers from an account again. - /// - /// Origin must be Signed and the sender should be the Admin of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - `who`: The account to be unfrozen. - /// - /// Emits `Thawed`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::thaw()] - fn thaw(origin, #[compact] id: T::AssetId, who: ::Source) { - let origin = ensure_signed(origin)?; - - let details = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &details.admin, Error::::NoPermission); - let who = T::Lookup::lookup(who)?; - ensure!(Account::::contains_key(id, &who), Error::::BalanceZero); - - Account::::mutate(id, &who, |a| a.is_frozen = false); - - Self::deposit_event(Event::::Thawed(id, who)); - } - - /// Disallow further unprivileged transfers for the asset class. - /// - /// Origin must be Signed and the sender should be the Freezer of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - /// Emits `Frozen`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::freeze_asset()] - fn freeze_asset(origin, #[compact] id: T::AssetId) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Asset::::try_mutate(id, |maybe_details| { - let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); - - d.is_frozen = true; - - Self::deposit_event(Event::::AssetFrozen(id)); - Ok(()) - }) - } - - /// Allow unprivileged transfers for the asset again. - /// - /// Origin must be Signed and the sender should be the Admin of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - /// Emits `Thawed`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::thaw_asset()] - fn thaw_asset(origin, #[compact] id: T::AssetId) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Asset::::try_mutate(id, |maybe_details| { - let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.admin, Error::::NoPermission); - - d.is_frozen = false; - - Self::deposit_event(Event::::AssetThawed(id)); - Ok(()) - }) - } - - /// Change the Owner of an asset. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - `owner`: The new Owner of this asset. - /// - /// Emits `OwnerChanged`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::transfer_ownership()] - fn transfer_ownership(origin, - #[compact] id: T::AssetId, - owner: ::Source, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let owner = T::Lookup::lookup(owner)?; - - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); - if details.owner == owner { return Ok(()) } - - // Move the deposit to the new owner. - T::Currency::repatriate_reserved(&details.owner, &owner, details.deposit, Reserved)?; - - details.owner = owner.clone(); - - Self::deposit_event(RawEvent::OwnerChanged(id, owner)); - Ok(()) - }) - } - - /// Change the Issuer, Admin and Freezer of an asset. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// - `id`: The identifier of the asset to be frozen. - /// - `issuer`: The new Issuer of this asset. - /// - `admin`: The new Admin of this asset. - /// - `freezer`: The new Freezer of this asset. - /// - /// Emits `TeamChanged`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::set_team()] - fn set_team(origin, - #[compact] id: T::AssetId, - issuer: ::Source, - admin: ::Source, - freezer: ::Source, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let issuer = T::Lookup::lookup(issuer)?; - let admin = T::Lookup::lookup(admin)?; - let freezer = T::Lookup::lookup(freezer)?; - - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); - - details.issuer = issuer.clone(); - details.admin = admin.clone(); - details.freezer = freezer.clone(); - - Self::deposit_event(RawEvent::TeamChanged(id, issuer, admin, freezer)); - Ok(()) - }) - } - - /// Set the maximum number of zombie accounts for an asset. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// Funds of sender are reserved according to the formula: - /// `AssetDepositBase + AssetDepositPerZombie * max_zombies` taking into account - /// any already reserved funds. - /// - /// - `id`: The identifier of the asset to update zombie count. - /// - `max_zombies`: The new number of zombies allowed for this asset. - /// - /// Emits `MaxZombiesChanged`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::set_max_zombies()] - fn set_max_zombies(origin, - #[compact] id: T::AssetId, - #[compact] max_zombies: u32, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - Asset::::try_mutate(id, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); - ensure!(max_zombies >= details.zombies, Error::::TooManyZombies); - - let new_deposit = T::AssetDepositPerZombie::get() - .saturating_mul(max_zombies.into()) - .saturating_add(T::AssetDepositBase::get()); - - if new_deposit > details.deposit { - T::Currency::reserve(&origin, new_deposit - details.deposit)?; - } else { - T::Currency::unreserve(&origin, details.deposit - new_deposit); - } - - details.max_zombies = max_zombies; - - Self::deposit_event(RawEvent::MaxZombiesChanged(id, max_zombies)); - Ok(()) - }) - } - - /// Set the metadata for an asset. - /// - /// NOTE: There is no `unset_metadata` call. Simply pass an empty name, symbol, - /// and 0 decimals to this function to remove the metadata of an asset and - /// return your deposit. - /// - /// Origin must be Signed and the sender should be the Owner of the asset `id`. - /// - /// Funds of sender are reserved according to the formula: - /// `MetadataDepositBase + MetadataDepositPerByte * (name.len + symbol.len)` taking into - /// account any already reserved funds. - /// - /// - `id`: The identifier of the asset to update. - /// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`. - /// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`. - /// - `decimals`: The number of decimals this asset uses to represent one unit. - /// - /// Emits `MaxZombiesChanged`. - /// - /// Weight: `O(1)` - #[weight = T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32)] - fn set_metadata(origin, - #[compact] id: T::AssetId, - name: Vec, - symbol: Vec, - decimals: u8, - ) -> DispatchResult { - let origin = ensure_signed(origin)?; - - ensure!(name.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); - ensure!(symbol.len() <= T::StringLimit::get() as usize, Error::::BadMetadata); - - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.owner, Error::::NoPermission); - - Metadata::::try_mutate_exists(id, |metadata| { - let bytes_used = name.len() + symbol.len(); - let old_deposit = match metadata { - Some(m) => m.deposit, - None => Default::default() - }; - - // Metadata is being removed - if bytes_used.is_zero() && decimals.is_zero() { - T::Currency::unreserve(&origin, old_deposit); - *metadata = None; - } else { - let new_deposit = T::MetadataDepositPerByte::get() - .saturating_mul(((name.len() + symbol.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); - - if new_deposit > old_deposit { - T::Currency::reserve(&origin, new_deposit - old_deposit)?; - } else { - T::Currency::unreserve(&origin, old_deposit - new_deposit); - } - - *metadata = Some(AssetMetadata { - deposit: new_deposit, - name: name.clone(), - symbol: symbol.clone(), - decimals, - }) - } - - Self::deposit_event(RawEvent::MetadataSet(id, name, symbol, decimals)); - Ok(()) - }) - } - } -} - // The main implementation block for the module. -impl Module { +impl Pallet { // Public immutables /// Get the asset `id` balance of `who`. diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 08852a7f3c..951e12c9c7 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -1874,11 +1874,15 @@ pub mod pallet_prelude { /// /// ## Upgrade guidelines: /// -/// 1. export metadata of the pallet for later checks -/// 2. generate the template upgrade for the pallet provided by decl_storage with environment -/// variable `PRINT_PALLET_UPGRADE`: `PRINT_PALLET_UPGRADE=1 cargo check -p my_pallet` -/// This template can be used as information it contains all information for storages, genesis -/// config and genesis build. +/// 1. Export the metadata of the pallet for later checks +/// - run your node with the pallet active +/// - query the metadata using the `state_getMetadata` RPC and curl, or use +/// `subsee -p > meta.json` +/// 2. generate the template upgrade for the pallet provided by decl_storage +/// with environment variable `PRINT_PALLET_UPGRADE`: +/// `PRINT_PALLET_UPGRADE=1 cargo check -p my_pallet` This template can be +/// used as information it contains all information for storages, genesis +/// config and genesis build. /// 3. reorganize pallet to have trait `Config`, `decl_*` macros, `ValidateUnsigned`, /// `ProvideInherent`, `Origin` all together in one file. Suggested order: /// * Config, @@ -1925,7 +1929,7 @@ pub mod pallet_prelude { /// impl Pallet { /// } /// ``` -/// and write inside all the call in decl_module with a few changes in the signature: +/// and write inside all the calls in decl_module with a few changes in the signature: /// - origin must now be written completely, e.g. `origin: OriginFor` /// - result type must be `DispatchResultWithPostInfo`, you need to write it and also you might /// need to put `Ok(().into())` at the end or the function.