// 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>;