Balance to Asset Balance Conversion (#9076)

* add BalanceConversion trait and implementation

* derive some useful traits on Imbalance

* Update frame/support/src/traits/tokens/fungibles/imbalance.rs

Co-authored-by: Xiliang Chen <xlchen1291@gmail.com>

* make BalanceConversion error type configurable

* add RuntimeDebug import and derive traits on other Imbalance

* formatting

* move BalanceConversion trait to frame-support

* add necessary trait import

Co-authored-by: Xiliang Chen <xlchen1291@gmail.com>
This commit is contained in:
Alexander Popiak
2021-07-20 16:55:54 +02:00
committed by GitHub
parent e17a014627
commit f538174847
7 changed files with 99 additions and 9 deletions
+1 -5
View File
@@ -144,7 +144,7 @@ use sp_runtime::{
traits::{AtLeast32BitUnsigned, Zero, StaticLookup, Saturating, CheckedSub, CheckedAdd, Bounded}
};
use codec::HasCompact;
use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}};
use frame_support::pallet_prelude::*;
use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, StoredMap};
use frame_support::traits::tokens::{WithdrawConsequence, DepositConsequence, fungibles};
use frame_system::Config as SystemConfig;
@@ -154,10 +154,6 @@ pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::{
dispatch::DispatchResult,
pallet_prelude::*,
};
use frame_system::pallet_prelude::*;
use super::*;
+27 -1
View File
@@ -19,7 +19,7 @@
use super::*;
use crate::{Error, mock::*};
use sp_runtime::TokenError;
use sp_runtime::{TokenError, traits::ConvertInto};
use frame_support::{assert_ok, assert_noop, traits::Currency};
use pallet_balances::Error as BalancesError;
@@ -699,3 +699,29 @@ fn force_asset_status_should_work(){
assert_eq!(Assets::total_supply(0), 200);
});
}
#[test]
fn balance_conversion_should_work() {
new_test_ext().execute_with(|| {
use frame_support::traits::tokens::BalanceConversion;
let id = 42;
assert_ok!(Assets::force_create(Origin::root(), id, 1, true, 10));
let not_sufficient = 23;
assert_ok!(Assets::force_create(Origin::root(), not_sufficient, 1, false, 10));
assert_eq!(
BalanceToAssetBalance::<Balances, Test, ConvertInto>::to_asset_balance(100, 1234),
Err(ConversionError::AssetMissing)
);
assert_eq!(
BalanceToAssetBalance::<Balances, Test, ConvertInto>::to_asset_balance(100, not_sufficient),
Err(ConversionError::AssetNotSufficient)
);
// 10 / 1 == 10 -> the conversion should 10x the value
assert_eq!(
BalanceToAssetBalance::<Balances, Test, ConvertInto>::to_asset_balance(100, id),
Ok(100 * 10)
);
});
}
+59
View File
@@ -20,6 +20,10 @@
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::traits::{fungible, tokens::BalanceConversion};
use sp_runtime::{FixedPointNumber, FixedPointOperand, FixedU128};
use sp_runtime::traits::Convert;
pub(super) type DepositBalanceOf<T, I = ()> =
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
@@ -177,3 +181,58 @@ impl From<TransferFlags> for DebitFlags {
}
}
}
/// Possible errors when converting between external and asset balances.
#[derive(Eq, PartialEq, Copy, Clone, RuntimeDebug, Encode, Decode)]
pub enum ConversionError {
/// The external minimum balance must not be zero.
MinBalanceZero,
/// The asset is not present in storage.
AssetMissing,
/// The asset is not sufficient and thus does not have a reliable `min_balance` so it cannot be converted.
AssetNotSufficient,
}
// Type alias for `frame_system`'s account id.
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
// This pallet's asset id and balance type.
type AssetIdOf<T, I> = <T as Config<I>>::AssetId;
type AssetBalanceOf<T, I> = <T as Config<I>>::Balance;
// Generic fungible balance type.
type BalanceOf<F, T> = <F as fungible::Inspect<AccountIdOf<T>>>::Balance;
/// Converts a balance value into an asset balance based on the ratio between the fungible's
/// minimum balance and the minimum asset balance.
pub struct BalanceToAssetBalance<F, T, CON, I = ()>(PhantomData<(F, T, CON, I)>);
impl<F, T, CON, I> BalanceConversion<BalanceOf<F, T>, AssetIdOf<T, I>, AssetBalanceOf<T, I>>
for BalanceToAssetBalance<F, T, CON, I>
where
F: fungible::Inspect<AccountIdOf<T>>,
T: Config<I>,
I: 'static,
CON: Convert<BalanceOf<F, T>, AssetBalanceOf<T, I>>,
BalanceOf<F, T>: FixedPointOperand + Zero,
AssetBalanceOf<T, I>: FixedPointOperand + Zero,
{
type Error = ConversionError;
/// Convert the given balance value into an asset balance based on the ratio between the fungible's
/// minimum balance and the minimum asset balance.
///
/// Will return `Err` if the asset is not found, not sufficient or the fungible's minimum balance is zero.
fn to_asset_balance(
balance: BalanceOf<F, T>,
asset_id: AssetIdOf<T, I>,
) -> Result<AssetBalanceOf<T, I>, ConversionError> {
let asset = Asset::<T, I>::get(asset_id).ok_or(ConversionError::AssetMissing)?;
// only sufficient assets have a min balance with reliable value
ensure!(asset.is_sufficient, ConversionError::AssetNotSufficient);
let min_balance = CON::convert(F::minimum_balance());
// make sure we don't divide by zero
ensure!(!min_balance.is_zero(), ConversionError::MinBalanceZero);
let balance = CON::convert(balance);
// balance * asset.min_balance / min_balance
Ok(FixedU128::saturating_from_rational(asset.min_balance, min_balance)
.saturating_mul_int(balance))
}
}
+2 -1
View File
@@ -25,6 +25,7 @@ pub mod nonfungible;
pub mod nonfungibles;
mod misc;
pub use misc::{
WithdrawConsequence, DepositConsequence, ExistenceRequirement, BalanceStatus, WithdrawReasons,
BalanceConversion, BalanceStatus, DepositConsequence,
ExistenceRequirement, WithdrawConsequence, WithdrawReasons,
};
pub use imbalance::Imbalance;
@@ -20,7 +20,7 @@
use super::*;
use sp_std::marker::PhantomData;
use sp_runtime::traits::Zero;
use sp_runtime::{RuntimeDebug, traits::Zero};
use super::misc::Balance;
use super::balanced::Balanced;
use crate::traits::misc::{TryDrop, SameOrOther};
@@ -39,6 +39,7 @@ pub trait HandleImbalanceDrop<Balance> {
///
/// Importantly, it has a special `Drop` impl, and cannot be created outside of this module.
#[must_use]
#[derive(RuntimeDebug, Eq, PartialEq)]
pub struct Imbalance<
B: Balance,
OnDrop: HandleImbalanceDrop<B>,
@@ -20,7 +20,7 @@
use super::*;
use sp_std::marker::PhantomData;
use sp_runtime::traits::Zero;
use sp_runtime::{RuntimeDebug, traits::Zero};
use super::fungibles::{AssetId, Balance};
use super::balanced::Balanced;
use crate::traits::misc::{TryDrop, SameOrOther};
@@ -37,6 +37,7 @@ pub trait HandleImbalanceDrop<AssetId, Balance> {
///
/// Importantly, it has a special `Drop` impl, and cannot be created outside of this module.
#[must_use]
#[derive(RuntimeDebug, Eq, PartialEq)]
pub struct Imbalance<
A: AssetId,
B: Balance,
@@ -167,3 +167,9 @@ impl<T: FullCodec + Copy + Eq + PartialEq + Debug> AssetId for T {}
/// Simple amalgamation trait to collect together properties for a Balance under one roof.
pub trait Balance: AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug {}
impl<T: AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug> Balance for T {}
/// Converts a balance value into an asset balance.
pub trait BalanceConversion<InBalance, AssetId, OutBalance> {
type Error;
fn to_asset_balance(balance: InBalance, asset_id: AssetId) -> Result<OutBalance, Self::Error>;
}