diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 333dbad836462..e8d7e7141e2c0 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -147,7 +147,7 @@ use sp_runtime::{ } }; 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; @@ -157,10 +157,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/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index 3ee8f9a9cfa47..12ab418e2c7d2 100644 --- a/frame/assets/src/tests.rs +++ b/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; @@ -625,3 +625,18 @@ fn force_asset_status_should_work(){ assert_eq!(Assets::total_supply(0), 200); }); } + +#[test] +fn balance_conversion_should_work() { + new_test_ext().execute_with(|| { + 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/frame/assets/src/types.rs b/frame/assets/src/types.rs index afd6b536cf18a..8a38d06070d51 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -20,6 +20,10 @@ use super::*; use frame_support::pallet_prelude::*; +use frame_support::traits::fungible; +use sp_runtime::{FixedPointNumber, FixedPointOperand, FixedU128}; +use sp_runtime::traits::Convert; + pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; @@ -177,3 +181,54 @@ impl From for DebitFlags { } } } + +/// Converts a balance value into an asset balance. +pub trait BalanceConversion { + fn to_asset_balance(balance: InBalance, asset_id: AssetId) -> Result; +} + +/// 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 AccountIdOf = ::AccountId; +type AssetIdOf = >::AssetId; +type AssetBalanceOf = >::Balance; +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, +{ + /// 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)) + } +}