diff --git a/substrate/frame/assets/src/lib.rs b/substrate/frame/assets/src/lib.rs index 5c1370ed28..5fe167df3f 100644 --- a/substrate/frame/assets/src/lib.rs +++ b/substrate/frame/assets/src/lib.rs @@ -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::*; diff --git a/substrate/frame/assets/src/tests.rs b/substrate/frame/assets/src/tests.rs index b8eb2e40f8..c2cf9acf29 100644 --- a/substrate/frame/assets/src/tests.rs +++ b/substrate/frame/assets/src/tests.rs @@ -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::::to_asset_balance(100, 1234), + Err(ConversionError::AssetMissing) + ); + assert_eq!( + BalanceToAssetBalance::::to_asset_balance(100, not_sufficient), + Err(ConversionError::AssetNotSufficient) + ); + // 10 / 1 == 10 -> the conversion should 10x the value + assert_eq!( + BalanceToAssetBalance::::to_asset_balance(100, id), + Ok(100 * 10) + ); + }); +} diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index afd6b536cf..478905eb68 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -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 = <>::Currency as Currency<::AccountId>>::Balance; @@ -177,3 +181,58 @@ impl From 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 = ::AccountId; +// This pallet's asset id and balance type. +type AssetIdOf = >::AssetId; +type AssetBalanceOf = >::Balance; +// Generic fungible balance type. +type BalanceOf = >>::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(PhantomData<(F, T, CON, I)>); +impl BalanceConversion, AssetIdOf, AssetBalanceOf> +for BalanceToAssetBalance +where + F: fungible::Inspect>, + T: Config, + I: 'static, + CON: Convert, AssetBalanceOf>, + BalanceOf: FixedPointOperand + Zero, + AssetBalanceOf: 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, + asset_id: AssetIdOf, + ) -> Result, ConversionError> { + let asset = Asset::::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)) + } +} diff --git a/substrate/frame/support/src/traits/tokens.rs b/substrate/frame/support/src/traits/tokens.rs index ac316b82b0..faf8ebfd30 100644 --- a/substrate/frame/support/src/traits/tokens.rs +++ b/substrate/frame/support/src/traits/tokens.rs @@ -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; diff --git a/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs b/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs index c084fa97fb..ab3694359c 100644 --- a/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs +++ b/substrate/frame/support/src/traits/tokens/fungible/imbalance.rs @@ -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 { /// /// 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, diff --git a/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs b/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs index ecc415cb56..9ecdeac1d4 100644 --- a/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs +++ b/substrate/frame/support/src/traits/tokens/fungibles/imbalance.rs @@ -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 { /// /// 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, diff --git a/substrate/frame/support/src/traits/tokens/misc.rs b/substrate/frame/support/src/traits/tokens/misc.rs index 0c55ac7918..97c111798c 100644 --- a/substrate/frame/support/src/traits/tokens/misc.rs +++ b/substrate/frame/support/src/traits/tokens/misc.rs @@ -167,3 +167,9 @@ impl 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 Balance for T {} + +/// Converts a balance value into an asset balance. +pub trait BalanceConversion { + type Error; + fn to_asset_balance(balance: InBalance, asset_id: AssetId) -> Result; +}