// Copyright 2019-2020
// by Centrality Investments Ltd.
// and Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
//! # Generic Asset Module
//!
//! The Generic Asset module provides functionality for handling accounts and asset balances.
//!
//! ## Overview
//!
//! The Generic Asset module provides functions for:
//!
//! - Creating a new kind of asset.
//! - Setting permissions of an asset.
//! - Getting and setting free balances.
//! - Retrieving total, reserved and unreserved balances.
//! - Repatriating a reserved balance to a beneficiary account.
//! - Transferring a balance between accounts (when not reserved).
//! - Slashing an account balance.
//! - Managing total issuance.
//! - Setting and managing locks.
//!
//! ### Terminology
//!
//! - **Staking Asset:** The asset for staking, to participate as Validators in the network.
//! - **Spending Asset:** The asset for payment, such as paying transfer fees, gas fees, etc.
//! - **Permissions:** A set of rules for a kind of asset, defining the allowed operations to the asset, and which
//! accounts are allowed to possess it.
//! - **Total Issuance:** The total number of units in existence in a system.
//! - **Free Balance:** The portion of a balance that is not reserved. The free balance is the only balance that matters
//! for most operations. When this balance falls below the existential deposit, most functionality of the account is
//! removed. When both it and the reserved balance are deleted, then the account is said to be dead.
//! - **Reserved Balance:** Reserved balance still belongs to the account holder, but is suspended. Reserved balance
//! can still be slashed, but only after all the free balance has been slashed. If the reserved balance falls below the
//! existential deposit then it and any related functionality will be deleted. When both it and the free balance are
//! deleted, then the account is said to be dead.
//! - **Imbalance:** A condition when some assets were credited or debited without equal and opposite accounting
//! (i.e. a difference between total issuance and account balances). Functions that result in an imbalance will
//! return an object of the `Imbalance` trait that can be managed within your runtime logic. (If an imbalance is
//! simply dropped, it should automatically maintain any book-keeping such as total issuance.)
//! - **Lock:** A freeze on a specified amount of an account's free balance until a specified block number. Multiple
//! locks always operate over the same funds, so they "overlay" rather than "stack".
//!
//! ### Implementations
//!
//! The Generic Asset module provides `AssetCurrency`, which implements the following traits. If these traits provide
//! the functionality that you need, you can avoid coupling with the Generic Asset module.
//!
//! - `Currency`: Functions for dealing with a fungible assets system.
//! - `ReservableCurrency`: Functions for dealing with assets that can be reserved from an account.
//! - `LockableCurrency`: Functions for dealing with accounts that allow liquidity restrictions.
//! - `Imbalance`: Functions for handling imbalances between total issuance in the system and account balances.
//! Must be used when a function creates new assets (e.g. a reward) or destroys some assets (e.g. a system fee).
//!
//! The Generic Asset module provides two types of `AssetCurrency` as follows.
//!
//! - `StakingAssetCurrency`: Currency for staking.
//! - `SpendingAssetCurrency`: Currency for payments such as transfer fee, gas fee.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
//! - `create`: Create a new kind of asset.
//! - `transfer`: Transfer some liquid free balance to another account.
//! - `update_permission`: Updates permission for a given `asset_id` and an account. The origin of this call
//! must have update permissions.
//! - `mint`: Mint an asset, increases its total issuance. The origin of this call must have mint permissions.
//! - `burn`: Burn an asset, decreases its total issuance. The origin of this call must have burn permissions.
//! - `create_reserved`: Create a new kind of reserved asset. The origin of this call must be root.
//!
//! ### Public Functions
//!
//! - `total_balance`: Get an account's total balance of an asset kind.
//! - `free_balance`: Get an account's free balance of an asset kind.
//! - `reserved_balance`: Get an account's reserved balance of an asset kind.
//! - `create_asset`: Creates an asset.
//! - `make_transfer`: Transfer some liquid free balance from one account to another.
//! This will not emit the `Transferred` event.
//! - `make_transfer_with_event`: Transfer some liquid free balance from one account to another.
//! This will emit the `Transferred` event.
//! - `reserve`: Moves an amount from free balance to reserved balance.
//! - `unreserve`: Move up to an amount from reserved balance to free balance. This function cannot fail.
//! - `mint_free`: Mint to an account's free balance.
//! - `burn_free`: Burn an account's free balance.
//! - `slash`: Deduct up to an amount from the combined balance of `who`, preferring to deduct from the
//! free balance. This function cannot fail.
//! - `slash_reserved`: Deduct up to an amount from reserved balance of an account. This function cannot fail.
//! - `repatriate_reserved`: Move up to an amount from reserved balance of an account to free balance of another
//! account.
//! - `check_permission`: Check permission to perform burn, mint or update.
//! - `ensure_can_withdraw`: Check if the account is able to make a withdrawal of the given amount
//! for the given reason.
//!
//! ### Usage
//!
//! The following examples show how to use the Generic Asset module in your custom module.
//!
//! ### Examples from the frame module
//!
//! The Fees module uses the `Currency` trait to handle fee charge/refund, and its types inherit from `Currency`:
//!
//! ```
//! use frame_support::{
//! dispatch,
//! traits::{Currency, ExistenceRequirement, WithdrawReason},
//! };
//! # pub trait Trait: frame_system::Trait {
//! # type Currency: Currency;
//! # }
//! type AssetOf = <::Currency as Currency<::AccountId>>::Balance;
//!
//! fn charge_fee(transactor: &T::AccountId, amount: AssetOf) -> dispatch::DispatchResult {
//! // ...
//! T::Currency::withdraw(
//! transactor,
//! amount,
//! WithdrawReason::TransactionPayment.into(),
//! ExistenceRequirement::KeepAlive,
//! )?;
//! // ...
//! Ok(())
//! }
//!
//! fn refund_fee(transactor: &T::AccountId, amount: AssetOf) -> dispatch::DispatchResult {
//! // ...
//! T::Currency::deposit_into_existing(transactor, amount)?;
//! // ...
//! Ok(())
//! }
//!
//! # fn main() {}
//! ```
//!
//! ## Genesis config
//!
//! The Generic Asset module depends on the [`GenesisConfig`](./struct.GenesisConfig.html).
#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode, HasCompact, Input, Output, Error as CodecError};
use sp_runtime::{RuntimeDebug, DispatchResult, DispatchError};
use sp_runtime::traits::{
CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, One, Saturating, SimpleArithmetic,
Zero, Bounded,
};
use sp_std::prelude::*;
use sp_std::{cmp, result, fmt::Debug};
use frame_support::{
decl_event, decl_module, decl_storage, ensure, decl_error,
traits::{
Currency, ExistenceRequirement, Imbalance, LockIdentifier, LockableCurrency, ReservableCurrency,
SignedImbalance, UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, TryDrop,
},
Parameter, StorageMap,
};
use frame_system::{self as system, ensure_signed, ensure_root};
mod mock;
mod tests;
pub use self::imbalances::{NegativeImbalance, PositiveImbalance};
pub trait Trait: frame_system::Trait {
type Balance: Parameter
+ Member
+ SimpleArithmetic
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug;
type AssetId: Parameter + Member + SimpleArithmetic + Default + Copy;
type Event: From> + Into<::Event>;
}
pub trait Subtrait: frame_system::Trait {
type Balance: Parameter
+ Member
+ SimpleArithmetic
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug;
type AssetId: Parameter + Member + SimpleArithmetic + Default + Copy;
}
impl Subtrait for T {
type Balance = T::Balance;
type AssetId = T::AssetId;
}
/// Asset creation options.
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct AssetOptions {
/// Initial issuance of this asset. All deposit to the creater of the asset.
#[codec(compact)]
pub initial_issuance: Balance,
/// Which accounts are allowed to possess this asset.
pub permissions: PermissionLatest,
}
/// Owner of an asset.
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub enum Owner {
/// No owner.
None,
/// Owned by an AccountId
Address(AccountId),
}
impl Default for Owner {
fn default() -> Self {
Owner::None
}
}
/// Asset permissions
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct PermissionsV1 {
/// Who have permission to update asset permission
pub update: Owner,
/// Who have permission to mint new asset
pub mint: Owner,
/// Who have permission to burn asset
pub burn: Owner,
}
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
#[repr(u8)]
enum PermissionVersionNumber {
V1 = 0,
}
/// Versioned asset permission
#[derive(Clone, PartialEq, Eq, RuntimeDebug)]
pub enum PermissionVersions {
V1(PermissionsV1),
}
/// Asset permission types
pub enum PermissionType {
/// Permission to burn asset permission
Burn,
/// Permission to mint new asset
Mint,
/// Permission to update asset
Update,
}
/// Alias to latest asset permissions
pub type PermissionLatest = PermissionsV1;
impl Default for PermissionVersions {
fn default() -> Self {
PermissionVersions::V1(Default::default())
}
}
impl Encode for PermissionVersions {
fn encode_to(&self, dest: &mut T) {
match self {
PermissionVersions::V1(payload) => {
dest.push(&PermissionVersionNumber::V1);
dest.push(payload);
},
}
}
}
impl codec::EncodeLike for PermissionVersions {}
impl Decode for PermissionVersions {
fn decode(input: &mut I) -> core::result::Result {
let version = PermissionVersionNumber::decode(input)?;
Ok(
match version {
PermissionVersionNumber::V1 => PermissionVersions::V1(Decode::decode(input)?)
}
)
}
}
impl Default for PermissionsV1 {
fn default() -> Self {
PermissionsV1 {
update: Owner::None,
mint: Owner::None,
burn: Owner::None,
}
}
}
impl Into> for PermissionVersions {
fn into(self) -> PermissionLatest {
match self {
PermissionVersions::V1(v1) => v1,
}
}
}
/// Converts the latest permission to other version.
impl Into> for PermissionLatest {
fn into(self) -> PermissionVersions {
PermissionVersions::V1(self)
}
}
decl_error! {
/// Error for the generic-asset module.
pub enum Error for Module {
/// No new assets id available.
NoIdAvailable,
/// Cannot transfer zero amount.
ZeroAmount,
/// The origin does not have enough permission to update permissions.
NoUpdatePermission,
/// The origin does not have permission to mint an asset.
NoMintPermission,
/// The origin does not have permission to burn an asset.
NoBurnPermission,
/// Total issuance got overflowed after minting.
TotalMintingOverflow,
/// Free balance got overflowed after minting.
FreeMintingOverflow,
/// Total issuance got underflowed after burning.
TotalBurningUnderflow,
/// Free balance got underflowed after burning.
FreeBurningUnderflow,
/// Asset id is already taken.
IdAlreadyTaken,
/// Asset id not available.
IdUnavailable,
/// The balance is too low to send amount.
InsufficientBalance,
/// The account liquidity restrictions prevent withdrawal.
LiquidityRestrictions,
}
}
decl_module! {
pub struct Module for enum Call where origin: T::Origin {
type Error = Error;
fn deposit_event() = default;
/// Create a new kind of asset.
fn create(origin, options: AssetOptions) -> DispatchResult {
let origin = ensure_signed(origin)?;
Self::create_asset(None, Some(origin), options)
}
/// Transfer some liquid free balance to another account.
pub fn transfer(origin, #[compact] asset_id: T::AssetId, to: T::AccountId, #[compact] amount: T::Balance) {
let origin = ensure_signed(origin)?;
ensure!(!amount.is_zero(), Error::::ZeroAmount);
Self::make_transfer_with_event(&asset_id, &origin, &to, amount)?;
}
/// Updates permission for a given `asset_id` and an account.
///
/// The `origin` must have `update` permission.
fn update_permission(
origin,
#[compact] asset_id: T::AssetId,
new_permission: PermissionLatest
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let permissions: PermissionVersions = new_permission.into();
if Self::check_permission(&asset_id, &origin, &PermissionType::Update) {
>::insert(asset_id, &permissions);
Self::deposit_event(RawEvent::PermissionUpdated(asset_id, permissions.into()));
Ok(())
} else {
Err(Error::::NoUpdatePermission)?
}
}
/// Mints an asset, increases its total issuance.
/// The origin must have `mint` permissions.
fn mint(origin, #[compact] asset_id: T::AssetId, to: T::AccountId, amount: T::Balance) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::mint_free(&asset_id, &who, &to, &amount)?;
Self::deposit_event(RawEvent::Minted(asset_id, to, amount));
Ok(())
}
/// Burns an asset, decreases its total issuance.
/// The `origin` must have `burn` permissions.
fn burn(origin, #[compact] asset_id: T::AssetId, to: T::AccountId, amount: T::Balance) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::burn_free(&asset_id, &who, &to, &amount)?;
Self::deposit_event(RawEvent::Burned(asset_id, to, amount));
Ok(())
}
/// Can be used to create reserved tokens.
/// Requires Root call.
fn create_reserved(
origin,
asset_id: T::AssetId,
options: AssetOptions
) -> DispatchResult {
ensure_root(origin)?;
Self::create_asset(Some(asset_id), None, options)
}
}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct BalanceLock {
pub id: LockIdentifier,
pub amount: Balance,
pub reasons: WithdrawReasons,
}
decl_storage! {
trait Store for Module as GenericAsset {
/// Total issuance of a given asset.
pub TotalIssuance get(fn total_issuance) build(|config: &GenesisConfig| {
let issuance = config.initial_balance * (config.endowed_accounts.len() as u32).into();
config.assets.iter().map(|id| (id.clone(), issuance)).collect::>()
}): map hasher(blake2_256) T::AssetId => T::Balance;
/// The free balance of a given asset under an account.
pub FreeBalance:
double_map hasher(blake2_256) T::AssetId, hasher(twox_128) T::AccountId => T::Balance;
/// The reserved balance of a given asset under an account.
pub ReservedBalance:
double_map hasher(blake2_256) T::AssetId, hasher(twox_128) T::AccountId => T::Balance;
/// Next available ID for user-created asset.
pub NextAssetId get(fn next_asset_id) config(): T::AssetId;
/// Permission options for a given asset.
pub Permissions get(fn get_permission):
map hasher(blake2_256) T::AssetId => PermissionVersions;
/// Any liquidity locks on some account balances.
pub Locks get(fn locks):
map hasher(blake2_256) T::AccountId => Vec>;
/// The identity of the asset which is the one that is designated for the chain's staking system.
pub StakingAssetId get(fn staking_asset_id) config(): T::AssetId;
/// The identity of the asset which is the one that is designated for paying the chain's transaction fee.
pub SpendingAssetId get(fn spending_asset_id) config(): T::AssetId;
}
add_extra_genesis {
config(assets): Vec;
config(initial_balance): T::Balance;
config(endowed_accounts): Vec;
build(|config: &GenesisConfig| {
config.assets.iter().for_each(|asset_id| {
config.endowed_accounts.iter().for_each(|account_id| {
>::insert(asset_id, account_id, &config.initial_balance);
});
});
});
}
}
decl_event!(
pub enum Event where
::AccountId,
::Balance,
::AssetId,
AssetOptions = AssetOptions<::Balance, ::AccountId>
{
/// Asset created (asset_id, creator, asset_options).
Created(AssetId, AccountId, AssetOptions),
/// Asset transfer succeeded (asset_id, from, to, amount).
Transferred(AssetId, AccountId, AccountId, Balance),
/// Asset permission updated (asset_id, new_permissions).
PermissionUpdated(AssetId, PermissionLatest),
/// New asset minted (asset_id, account, amount).
Minted(AssetId, AccountId, Balance),
/// Asset burned (asset_id, account, amount).
Burned(AssetId, AccountId, Balance),
}
);
impl Module {
// PUBLIC IMMUTABLES
/// Get an account's total balance of an asset kind.
pub fn total_balance(asset_id: &T::AssetId, who: &T::AccountId) -> T::Balance {
Self::free_balance(asset_id, who) + Self::reserved_balance(asset_id, who)
}
/// Get an account's free balance of an asset kind.
pub fn free_balance(asset_id: &T::AssetId, who: &T::AccountId) -> T::Balance {
>::get(asset_id, who)
}
/// Get an account's reserved balance of an asset kind.
pub fn reserved_balance(asset_id: &T::AssetId, who: &T::AccountId) -> T::Balance {
>::get(asset_id, who)
}
/// Mint to an account's free balance, without event
pub fn mint_free(
asset_id: &T::AssetId,
who: &T::AccountId,
to: &T::AccountId,
amount: &T::Balance,
) -> DispatchResult {
if Self::check_permission(asset_id, who, &PermissionType::Mint) {
let original_free_balance = Self::free_balance(&asset_id, &to);
let current_total_issuance = >::get(asset_id);
let new_total_issuance = current_total_issuance.checked_add(&amount)
.ok_or(Error::::TotalMintingOverflow)?;
let value = original_free_balance.checked_add(&amount)
.ok_or(Error::::FreeMintingOverflow)?;
>::insert(asset_id, new_total_issuance);
Self::set_free_balance(&asset_id, &to, value);
Ok(())
} else {
Err(Error::::NoMintPermission)?
}
}
/// Burn an account's free balance, without event
pub fn burn_free(
asset_id: &T::AssetId,
who: &T::AccountId,
to: &T::AccountId,
amount: &T::Balance,
) -> DispatchResult {
if Self::check_permission(asset_id, who, &PermissionType::Burn) {
let original_free_balance = Self::free_balance(asset_id, to);
let current_total_issuance = >::get(asset_id);
let new_total_issuance = current_total_issuance.checked_sub(amount)
.ok_or(Error::::TotalBurningUnderflow)?;
let value = original_free_balance.checked_sub(amount)
.ok_or(Error::::FreeBurningUnderflow)?;
>::insert(asset_id, new_total_issuance);
Self::set_free_balance(asset_id, to, value);
Ok(())
} else {
Err(Error::::NoBurnPermission)?
}
}
/// Creates an asset.
///
/// # Arguments
/// * `asset_id`: An ID of a reserved asset.
/// If not provided, a user-generated asset will be created with the next available ID.
/// * `from_account`: The initiator account of this call
/// * `asset_options`: Asset creation options.
///
pub fn create_asset(
asset_id: Option,
from_account: Option,
options: AssetOptions,
) -> DispatchResult {
let asset_id = if let Some(asset_id) = asset_id {
ensure!(!>::exists(&asset_id), Error::::IdAlreadyTaken);
ensure!(asset_id < Self::next_asset_id(), Error::::IdUnavailable);
asset_id
} else {
let asset_id = Self::next_asset_id();
let next_id = asset_id
.checked_add(&One::one())
.ok_or(Error::::NoIdAvailable)?;
>::put(next_id);
asset_id
};
let account_id = from_account.unwrap_or_default();
let permissions: PermissionVersions = options.permissions.clone().into();
>::insert(asset_id, &options.initial_issuance);
>::insert(&asset_id, &account_id, &options.initial_issuance);
>::insert(&asset_id, permissions);
Self::deposit_event(RawEvent::Created(asset_id, account_id, options));
Ok(())
}
/// Transfer some liquid free balance from one account to another.
/// This will not emit the `Transferred` event.
pub fn make_transfer(
asset_id: &T::AssetId,
from: &T::AccountId,
to: &T::AccountId,
amount: T::Balance
) -> DispatchResult {
let new_balance = Self::free_balance(asset_id, from)
.checked_sub(&amount)
.ok_or(Error::::InsufficientBalance)?;
Self::ensure_can_withdraw(asset_id, from, amount, WithdrawReason::Transfer.into(), new_balance)?;
if from != to {
>::mutate(asset_id, from, |balance| *balance -= amount);
>::mutate(asset_id, to, |balance| *balance += amount);
}
Ok(())
}
/// Transfer some liquid free balance from one account to another.
/// This will emit the `Transferred` event.
pub fn make_transfer_with_event(
asset_id: &T::AssetId,
from: &T::AccountId,
to: &T::AccountId,
amount: T::Balance,
) -> DispatchResult {
Self::make_transfer(asset_id, from, to, amount)?;
if from != to {
Self::deposit_event(RawEvent::Transferred(*asset_id, from.clone(), to.clone(), amount));
}
Ok(())
}
/// Move `amount` from free balance to reserved balance.
///
/// If the free balance is lower than `amount`, then no funds will be moved and an `Err` will
/// be returned. This is different behavior than `unreserve`.
pub fn reserve(asset_id: &T::AssetId, who: &T::AccountId, amount: T::Balance)
-> DispatchResult
{
// Do we need to consider that this is an atomic transaction?
let original_reserve_balance = Self::reserved_balance(asset_id, who);
let original_free_balance = Self::free_balance(asset_id, who);
if original_free_balance < amount {
Err(Error::::InsufficientBalance)?
}
let new_reserve_balance = original_reserve_balance + amount;
Self::set_reserved_balance(asset_id, who, new_reserve_balance);
let new_free_balance = original_free_balance - amount;
Self::set_free_balance(asset_id, who, new_free_balance);
Ok(())
}
/// Moves up to `amount` from reserved balance to free balance. This function cannot fail.
///
/// As many assets up to `amount` will be moved as possible. If the reserve balance of `who`
/// is less than `amount`, then the remaining amount will be returned.
/// NOTE: This is different behavior than `reserve`.
pub fn unreserve(asset_id: &T::AssetId, who: &T::AccountId, amount: T::Balance) -> T::Balance {
let b = Self::reserved_balance(asset_id, who);
let actual = sp_std::cmp::min(b, amount);
let original_free_balance = Self::free_balance(asset_id, who);
let new_free_balance = original_free_balance + actual;
Self::set_free_balance(asset_id, who, new_free_balance);
Self::set_reserved_balance(asset_id, who, b - actual);
amount - actual
}
/// Deduct up to `amount` from the combined balance of `who`, preferring to deduct from the
/// free balance. This function cannot fail.
///
/// As much funds up to `amount` will be deducted as possible. If this is less than `amount`
/// then `Some(remaining)` will be returned. Full completion is given by `None`.
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
pub fn slash(asset_id: &T::AssetId, who: &T::AccountId, amount: T::Balance) -> Option {
let free_balance = Self::free_balance(asset_id, who);
let free_slash = sp_std::cmp::min(free_balance, amount);
let new_free_balance = free_balance - free_slash;
Self::set_free_balance(asset_id, who, new_free_balance);
if free_slash < amount {
Self::slash_reserved(asset_id, who, amount - free_slash)
} else {
None
}
}
/// Deducts up to `amount` from reserved balance of `who`. This function cannot fail.
///
/// As much funds up to `amount` will be deducted as possible. If the reserve balance of `who`
/// is less than `amount`, then a non-zero second item will be returned.
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
pub fn slash_reserved(asset_id: &T::AssetId, who: &T::AccountId, amount: T::Balance) -> Option {
let original_reserve_balance = Self::reserved_balance(asset_id, who);
let slash = sp_std::cmp::min(original_reserve_balance, amount);
let new_reserve_balance = original_reserve_balance - slash;
Self::set_reserved_balance(asset_id, who, new_reserve_balance);
if amount == slash {
None
} else {
Some(amount - slash)
}
}
/// Move up to `amount` from reserved balance of account `who` to free balance of account
/// `beneficiary`.
///
/// As much funds up to `amount` will be moved as possible. If this is less than `amount`, then
/// the `remaining` would be returned, else `Zero::zero()`.
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
pub fn repatriate_reserved(
asset_id: &T::AssetId,
who: &T::AccountId,
beneficiary: &T::AccountId,
amount: T::Balance,
) -> T::Balance {
let b = Self::reserved_balance(asset_id, who);
let slash = sp_std::cmp::min(b, amount);
let original_free_balance = Self::free_balance(asset_id, beneficiary);
let new_free_balance = original_free_balance + slash;
Self::set_free_balance(asset_id, beneficiary, new_free_balance);
let new_reserve_balance = b - slash;
Self::set_reserved_balance(asset_id, who, new_reserve_balance);
amount - slash
}
/// Check permission to perform burn, mint or update.
///
/// # Arguments
/// * `asset_id`: A `T::AssetId` type that contains the `asset_id`, which has the permission embedded.
/// * `who`: A `T::AccountId` type that contains the `account_id` for which to check permissions.
/// * `what`: The permission to check.
///
pub fn check_permission(asset_id: &T::AssetId, who: &T::AccountId, what: &PermissionType) -> bool {
let permission_versions: PermissionVersions = Self::get_permission(asset_id);
let permission = permission_versions.into();
match (what, permission) {
(
PermissionType::Burn,
PermissionLatest {
burn: Owner::Address(account),
..
},
) => account == *who,
(
PermissionType::Mint,
PermissionLatest {
mint: Owner::Address(account),
..
},
) => account == *who,
(
PermissionType::Update,
PermissionLatest {
update: Owner::Address(account),
..
},
) => account == *who,
_ => false,
}
}
/// Return `Ok` iff the account is able to make a withdrawal of the given amount
/// for the given reason.
///
/// `Err(...)` with the reason why not otherwise.
pub fn ensure_can_withdraw(
asset_id: &T::AssetId,
who: &T::AccountId,
_amount: T::Balance,
reasons: WithdrawReasons,
new_balance: T::Balance,
) -> DispatchResult {
if asset_id != &Self::staking_asset_id() {
return Ok(());
}
let locks = Self::locks(who);
if locks.is_empty() {
return Ok(());
}
if Self::locks(who)
.into_iter().all(|l| new_balance >= l.amount || !l.reasons.intersects(reasons))
{
Ok(())
} else {
Err(Error::::LiquidityRestrictions)?
}
}
// PRIVATE MUTABLES
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
fn set_reserved_balance(asset_id: &T::AssetId, who: &T::AccountId, balance: T::Balance) {
>::insert(asset_id, who, &balance);
}
/// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that
/// the caller will do this.
fn set_free_balance(asset_id: &T::AssetId, who: &T::AccountId, balance: T::Balance) {
>::insert(asset_id, who, &balance);
}
fn set_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
let mut new_lock = Some(BalanceLock {
id,
amount,
reasons,
});
let mut locks = >::locks(who)
.into_iter()
.filter_map(|l| {
if l.id == id {
new_lock.take()
} else {
Some(l)
}
})
.collect::>();
if let Some(lock) = new_lock {
locks.push(lock)
}
>::insert(who, locks);
}
fn extend_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
let mut new_lock = Some(BalanceLock {
id,
amount,
reasons,
});
let mut locks = >::locks(who)
.into_iter()
.filter_map(|l| {
if l.id == id {
new_lock.take().map(|nl| BalanceLock {
id: l.id,
amount: l.amount.max(nl.amount),
reasons: l.reasons | nl.reasons,
})
} else {
Some(l)
}
})
.collect::>();
if let Some(lock) = new_lock {
locks.push(lock)
}
>::insert(who, locks);
}
fn remove_lock(id: LockIdentifier, who: &T::AccountId) {
let mut locks = >::locks(who);
locks.retain(|l| l.id != id);
>::insert(who, locks);
}
}
pub trait AssetIdProvider {
type AssetId;
fn asset_id() -> Self::AssetId;
}
// wrapping these imbalanes in a private module is necessary to ensure absolute privacy
// of the inner member.
mod imbalances {
use super::{
result, AssetIdProvider, Imbalance, Saturating, StorageMap, Subtrait, Zero, TryDrop
};
use sp_std::mem;
/// Opaque, move-only struct with private fields that serves as a token denoting that
/// funds have been created without any equal and opposite accounting.
#[must_use]
pub struct PositiveImbalance>(
T::Balance,
sp_std::marker::PhantomData,
);
impl PositiveImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
pub fn new(amount: T::Balance) -> Self {
PositiveImbalance(amount, Default::default())
}
}
/// Opaque, move-only struct with private fields that serves as a token denoting that
/// funds have been destroyed without any equal and opposite accounting.
#[must_use]
pub struct NegativeImbalance>(
T::Balance,
sp_std::marker::PhantomData,
);
impl NegativeImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
pub fn new(amount: T::Balance) -> Self {
NegativeImbalance(amount, Default::default())
}
}
impl TryDrop for PositiveImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
fn try_drop(self) -> result::Result<(), Self> {
self.drop_zero()
}
}
impl Imbalance for PositiveImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
type Opposite = NegativeImbalance;
fn zero() -> Self {
Self::new(Zero::zero())
}
fn drop_zero(self) -> result::Result<(), Self> {
if self.0.is_zero() {
Ok(())
} else {
Err(self)
}
}
fn split(self, amount: T::Balance) -> (Self, Self) {
let first = self.0.min(amount);
let second = self.0 - first;
mem::forget(self);
(Self::new(first), Self::new(second))
}
fn merge(mut self, other: Self) -> Self {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
self
}
fn subsume(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
}
fn offset(self, other: Self::Opposite) -> result::Result {
let (a, b) = (self.0, other.0);
mem::forget((self, other));
if a >= b {
Ok(Self::new(a - b))
} else {
Err(NegativeImbalance::new(b - a))
}
}
fn peek(&self) -> T::Balance {
self.0.clone()
}
}
impl TryDrop for NegativeImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
fn try_drop(self) -> result::Result<(), Self> {
self.drop_zero()
}
}
impl Imbalance for NegativeImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
type Opposite = PositiveImbalance;
fn zero() -> Self {
Self::new(Zero::zero())
}
fn drop_zero(self) -> result::Result<(), Self> {
if self.0.is_zero() {
Ok(())
} else {
Err(self)
}
}
fn split(self, amount: T::Balance) -> (Self, Self) {
let first = self.0.min(amount);
let second = self.0 - first;
mem::forget(self);
(Self::new(first), Self::new(second))
}
fn merge(mut self, other: Self) -> Self {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
self
}
fn subsume(&mut self, other: Self) {
self.0 = self.0.saturating_add(other.0);
mem::forget(other);
}
fn offset(self, other: Self::Opposite) -> result::Result {
let (a, b) = (self.0, other.0);
mem::forget((self, other));
if a >= b {
Ok(Self::new(a - b))
} else {
Err(PositiveImbalance::new(b - a))
}
}
fn peek(&self) -> T::Balance {
self.0.clone()
}
}
impl Drop for PositiveImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
/// Basic drop handler will just square up the total issuance.
fn drop(&mut self) {
>>::mutate(&U::asset_id(), |v| *v = v.saturating_add(self.0));
}
}
impl Drop for NegativeImbalance
where
T: Subtrait,
U: AssetIdProvider,
{
/// Basic drop handler will just square up the total issuance.
fn drop(&mut self) {
>>::mutate(&U::asset_id(), |v| *v = v.saturating_sub(self.0));
}
}
}
// TODO: #2052
// Somewhat ugly hack in order to gain access to module's `increase_total_issuance_by`
// using only the Subtrait (which defines only the types that are not dependent
// on Positive/NegativeImbalance). Subtrait must be used otherwise we end up with a
// circular dependency with Trait having some types be dependent on PositiveImbalance
// and PositiveImbalance itself depending back on Trait for its Drop impl (and thus
// its type declaration).
// This works as long as `increase_total_issuance_by` doesn't use the Imbalance
// types (basically for charging fees).
// This should eventually be refactored so that the three type items that do
// depend on the Imbalance type (TransactionPayment, TransferPayment, DustRemoval)
// are placed in their own SRML module.
struct ElevatedTrait(T);
impl Clone for ElevatedTrait {
fn clone(&self) -> Self {
unimplemented!()
}
}
impl PartialEq for ElevatedTrait {
fn eq(&self, _: &Self) -> bool {
unimplemented!()
}
}
impl Eq for ElevatedTrait {}
impl frame_system::Trait for ElevatedTrait {
type Origin = T::Origin;
type Call = T::Call;
type Index = T::Index;
type BlockNumber = T::BlockNumber;
type Hash = T::Hash;
type Hashing = T::Hashing;
type AccountId = T::AccountId;
type Lookup = T::Lookup;
type Header = T::Header;
type Event = ();
type MaximumBlockWeight = T::MaximumBlockWeight;
type MaximumBlockLength = T::MaximumBlockLength;
type AvailableBlockRatio = T::AvailableBlockRatio;
type BlockHashCount = T::BlockHashCount;
type Version = T::Version;
type ModuleToIndex = ();
}
impl Trait for ElevatedTrait {
type Balance = T::Balance;
type AssetId = T::AssetId;
type Event = ();
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct AssetCurrency(sp_std::marker::PhantomData, sp_std::marker::PhantomData);
impl Currency for AssetCurrency
where
T: Trait,
U: AssetIdProvider,
{
type Balance = T::Balance;
type PositiveImbalance = PositiveImbalance;
type NegativeImbalance = NegativeImbalance;
fn total_balance(who: &T::AccountId) -> Self::Balance {
Self::free_balance(&who) + Self::reserved_balance(&who)
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
>::free_balance(&U::asset_id(), &who)
}
/// Returns the total staking asset issuance
fn total_issuance() -> Self::Balance {
>::total_issuance(U::asset_id())
}
fn minimum_balance() -> Self::Balance {
Zero::zero()
}
fn transfer(
transactor: &T::AccountId,
dest: &T::AccountId,
value: Self::Balance,
_: ExistenceRequirement, // no existential deposit policy for generic asset
) -> DispatchResult {
>::make_transfer(&U::asset_id(), transactor, dest, value)
}
fn ensure_can_withdraw(
who: &T::AccountId,
amount: Self::Balance,
reasons: WithdrawReasons,
new_balance: Self::Balance,
) -> DispatchResult {
>::ensure_can_withdraw(&U::asset_id(), who, amount, reasons, new_balance)
}
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
_: ExistenceRequirement, // no existential deposit policy for generic asset
) -> result::Result {
let new_balance = Self::free_balance(who)
.checked_sub(&value)
.ok_or(Error::::InsufficientBalance)?;
Self::ensure_can_withdraw(who, value, reasons, new_balance)?;
>::set_free_balance(&U::asset_id(), who, new_balance);
Ok(NegativeImbalance::new(value))
}
fn deposit_into_existing(
who: &T::AccountId,
value: Self::Balance,
) -> result::Result {
// No existential deposit rule and creation fee in GA. `deposit_into_existing` is same with `deposit_creating`.
Ok(Self::deposit_creating(who, value))
}
fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance {
let (imbalance, _) = Self::make_free_balance_be(who, Self::free_balance(who) + value);
if let SignedImbalance::Positive(p) = imbalance {
p
} else {
// Impossible, but be defensive.
Self::PositiveImbalance::zero()
}
}
fn make_free_balance_be(
who: &T::AccountId,
balance: Self::Balance,
) -> (
SignedImbalance,
UpdateBalanceOutcome,
) {
let original = >::free_balance(&U::asset_id(), who);
let imbalance = if original <= balance {
SignedImbalance::Positive(PositiveImbalance::new(balance - original))
} else {
SignedImbalance::Negative(NegativeImbalance::new(original - balance))
};
>::set_free_balance(&U::asset_id(), who, balance);
(imbalance, UpdateBalanceOutcome::Updated)
}
fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool {
>::free_balance(&U::asset_id(), &who) >= value
}
fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) {
let remaining = >::slash(&U::asset_id(), who, value);
if let Some(r) = remaining {
(NegativeImbalance::new(value - r), r)
} else {
(NegativeImbalance::new(value), Zero::zero())
}
}
fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance {
>::mutate(&U::asset_id(), |issued|
issued.checked_sub(&amount).unwrap_or_else(|| {
amount = *issued;
Zero::zero()
})
);
PositiveImbalance::new(amount)
}
fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance {
>::mutate(&U::asset_id(), |issued|
*issued = issued.checked_add(&amount).unwrap_or_else(|| {
amount = Self::Balance::max_value() - *issued;
Self::Balance::max_value()
})
);
NegativeImbalance::new(amount)
}
}
impl ReservableCurrency for AssetCurrency
where
T: Trait,
U: AssetIdProvider,
{
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
Self::free_balance(who)
.checked_sub(&value)
.map_or(false, |new_balance|
>::ensure_can_withdraw(
&U::asset_id(), who, value, WithdrawReason::Reserve.into(), new_balance
).is_ok()
)
}
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
>::reserved_balance(&U::asset_id(), &who)
}
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
>::reserve(&U::asset_id(), who, value)
}
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
>::unreserve(&U::asset_id(), who, value)
}
fn slash_reserved(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) {
let b = Self::reserved_balance(&who.clone());
let slash = cmp::min(b, value);
>::set_reserved_balance(&U::asset_id(), who, b - slash);
(NegativeImbalance::new(slash), value - slash)
}
fn repatriate_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
value: Self::Balance,
) -> result::Result {
Ok(>::repatriate_reserved(&U::asset_id(), slashed, beneficiary, value))
}
}
pub struct StakingAssetIdProvider(sp_std::marker::PhantomData);
impl AssetIdProvider for StakingAssetIdProvider {
type AssetId = T::AssetId;
fn asset_id() -> Self::AssetId {
>::staking_asset_id()
}
}
pub struct SpendingAssetIdProvider(sp_std::marker::PhantomData);
impl AssetIdProvider for SpendingAssetIdProvider {
type AssetId = T::AssetId;
fn asset_id() -> Self::AssetId {
>::spending_asset_id()
}
}
impl LockableCurrency for AssetCurrency>
where
T: Trait,
T::Balance: MaybeSerializeDeserialize + Debug,
{
type Moment = T::BlockNumber;
fn set_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
>::set_lock(id, who, amount, reasons)
}
fn extend_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
>::extend_lock(id, who, amount, reasons)
}
fn remove_lock(id: LockIdentifier, who: &T::AccountId) {
>::remove_lock(id, who)
}
}
pub type StakingAssetCurrency = AssetCurrency>;
pub type SpendingAssetCurrency = AssetCurrency>;