diff --git a/pallets/flexible-fee/Cargo.toml b/pallets/flexible-fee/Cargo.toml index 6e4285a9e..c90cfbe81 100644 --- a/pallets/flexible-fee/Cargo.toml +++ b/pallets/flexible-fee/Cargo.toml @@ -24,6 +24,7 @@ bifrost-asset-registry = { workspace = true } polkadot-parachain-primitives = { workspace = true } log = { workspace = true } xcm = { workspace = true } +sp-core = { workspace = true } [dev-dependencies] orml-tokens = { workspace = true } @@ -55,6 +56,7 @@ std = [ "cumulus-primitives-core/std", "bifrost-asset-registry/std", "pallet-xcm/std", + "sp-core/std", ] runtime-benchmarks = [ diff --git a/pallets/flexible-fee/src/lib.rs b/pallets/flexible-fee/src/lib.rs index 6473b6241..f9e12f773 100644 --- a/pallets/flexible-fee/src/lib.rs +++ b/pallets/flexible-fee/src/lib.rs @@ -20,9 +20,10 @@ pub use crate::pallet::*; use bifrost_primitives::{ - currency::WETH, + currency::{VGLMR, VMANTA, WETH}, traits::{FeeGetter, XcmDestWeightAndFeeHandler}, - AccountFeeCurrency, CurrencyId, ExtraFeeName, TryConvertFrom, XcmOperationType, BNC, + AccountFeeCurrency, BalanceCmp, CurrencyId, ExtraFeeName, TryConvertFrom, XcmOperationType, + BNC, DOT, GLMR, MANTA, VBNC, VDOT, }; use bifrost_xcm_interface::{polkadot::RelaychainCall, traits::parachains, PolkadotXcmCall}; use core::convert::Into; @@ -30,6 +31,8 @@ use cumulus_primitives_core::ParaId; use frame_support::{ pallet_prelude::*, traits::{ + fungibles::Inspect, + tokens::{Fortitude, Preservation}, Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, ReservableCurrency, WithdrawReasons, }, @@ -42,12 +45,13 @@ use orml_traits::MultiCurrency; use pallet_transaction_payment::OnChargeTransaction; use polkadot_parachain_primitives::primitives::Sibling; use sp_arithmetic::traits::{CheckedAdd, SaturatedConversion, UniqueSaturatedInto}; +use sp_core::U256; use sp_runtime::{ traits::{AccountIdConversion, DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero}, transaction_validity::TransactionValidityError, BoundedVec, }; -use sp_std::{boxed::Box, vec, vec::Vec}; +use sp_std::{boxed::Box, cmp::Ordering, vec, vec::Vec}; pub use weights::WeightInfo; use xcm::{prelude::Unlimited, v4::prelude::*}; use zenlink_protocol::{AssetBalance, AssetId, ExportZenlink}; @@ -79,6 +83,8 @@ pub enum TargetChain { #[frame_support::pallet] pub mod pallet { use super::*; + use bifrost_primitives::Balance; + use frame_support::traits::fungibles::Inspect; #[pallet::config] pub trait Config: frame_system::Config + pallet_transaction_payment::Config { @@ -87,11 +93,8 @@ pub mod pallet { /// Weight information for the extrinsics in this module. type WeightInfo: WeightInfo; /// Handler for both NativeCurrency and MultiCurrency - type MultiCurrency: MultiCurrency< - Self::AccountId, - CurrencyId = CurrencyId, - Balance = PalletBalanceOf, - >; + type MultiCurrency: MultiCurrency> + + Inspect; /// The currency type in which fees will be paid. type Currency: Currency + ReservableCurrency; /// Handler for the unbalanced decrease @@ -210,6 +213,9 @@ pub mod pallet { DexFailedToGetAmountInByPath, UnweighableMessage, XcmExecutionFailed, + /// Maximum number of currencies reached. + MaxCurrenciesReached, + CurrencyNotSupport, } #[pallet::call] @@ -650,7 +656,124 @@ where /// Provides account's fee payment asset or default fee asset ( Native asset ) impl AccountFeeCurrency for Pallet { - fn get(who: &T::AccountId) -> CurrencyId { - Pallet::::get_user_default_fee_currency(who).unwrap_or_else(|| WETH) + type Error = Error; + + /// Determines the appropriate currency to be used for paying transaction fees based on a + /// prioritized order: + /// 1. User's default fee currency (`UserDefaultFeeCurrency`) + /// 2. WETH + /// 3. Currencies in the `UniversalFeeCurrencyOrderList` + /// + /// The method first checks if the balance of the highest-priority currency is sufficient to + /// cover the fee.If the balance is insufficient, it iterates through the list of currencies in + /// priority order.If no currency has a sufficient balance, it returns the currency with the + /// highest balance. + fn get_fee_currency(account: &T::AccountId, fee: U256) -> Result> { + let fee: u128 = fee.unique_saturated_into(); + let priority_currency = Pallet::::get_user_default_fee_currency(account); + let mut currency_list = Pallet::::get_universal_fee_currency_order_list(); + + let first_item_index = 0; + currency_list + .try_insert(first_item_index, WETH) + .map_err(|_| Error::::MaxCurrenciesReached)?; + + // When all currency balances are insufficient, return the one with the highest balance + let mut hopeless_currency = WETH; + + if let Some(currency) = priority_currency { + currency_list + .try_insert(first_item_index, currency) + .map_err(|_| Error::::MaxCurrenciesReached)?; + hopeless_currency = currency; + } + + for maybe_currency in currency_list.iter() { + let comp_res = Self::cmp_with_precision(account, maybe_currency, fee, 18)?; + + match comp_res { + Ordering::Less => { + // Get the currency with the highest balance + let hopeless_currency_balance = T::MultiCurrency::reducible_balance( + hopeless_currency, + account, + Preservation::Preserve, + Fortitude::Polite, + ); + let maybe_currency_balance = T::MultiCurrency::reducible_balance( + *maybe_currency, + account, + Preservation::Preserve, + Fortitude::Polite, + ); + hopeless_currency = match hopeless_currency_balance.cmp(&maybe_currency_balance) + { + Ordering::Less => *maybe_currency, + _ => hopeless_currency, + }; + continue; + }, + Ordering::Equal => return Ok(*maybe_currency), + Ordering::Greater => return Ok(*maybe_currency), + }; + } + + return Ok(hopeless_currency); + } +} + +impl BalanceCmp for Pallet { + type Error = Error; + + /// Compares the balance of a specific `currency` for a given `account` against an `amount` + /// while considering different currency precisions. + /// + /// # Parameters + /// - `account`: The account ID whose balance will be checked. + /// - `currency`: The currency ID to be compared. + /// - `amount`: The amount to compare against the account's balance, with the precision + /// specified by `amount_precision`. + /// - `amount_precision`: The precision of the `amount` specified. If greater than 18, the + /// precision of the `currency` will be adjusted accordingly. + /// + /// # Returns + /// - `Ok(std::cmp::Ordering)`: Returns the ordering result (`Less`, `Equal`, `Greater`) based + /// on the comparison between the adjusted balance and the adjusted amount. + /// - `Err(Error)`: Returns an error if the currency is not supported. + fn cmp_with_precision( + account: &T::AccountId, + currency: &CurrencyId, + amount: u128, + amount_precision: u32, + ) -> Result> { + // Get the reducible balance for the specified account and currency. + let mut balance = T::MultiCurrency::reducible_balance( + *currency, + account, + Preservation::Preserve, + Fortitude::Polite, + ); + + // Define the standard precision as 18 decimal places. + let standard_precision: u32 = amount_precision.max(18); + + // Adjust the amount to the standard precision. + let precision_offset = standard_precision.saturating_sub(amount_precision); + let adjust_precision = 10u128.pow(precision_offset); + let amount = amount.saturating_mul(adjust_precision); + + // Adjust the balance based on currency type. + let balance_precision_offset = match *currency { + WETH | GLMR | VGLMR | MANTA | VMANTA => standard_precision.saturating_sub(18), + BNC | VBNC => standard_precision.saturating_sub(12), + DOT | VDOT => standard_precision.saturating_sub(10), + _ => return Err(Error::::CurrencyNotSupport), + }; + + // Apply precision adjustment to balance. + balance = balance.saturating_mul(10u128.pow(balance_precision_offset)); + + // Compare the adjusted balance with the input amount. + Ok(balance.cmp(&amount)) } } diff --git a/pallets/flexible-fee/src/mock.rs b/pallets/flexible-fee/src/mock.rs index 6ed57ad38..e263a82a3 100644 --- a/pallets/flexible-fee/src/mock.rs +++ b/pallets/flexible-fee/src/mock.rs @@ -523,6 +523,27 @@ impl bifrost_vtoken_voting::Config for Test { type WeightInfo = (); } +impl AccountFeeCurrency for Test { + type Error = Error; + + fn get_fee_currency(account: &AccountId32, fee: U256) -> Result { + Pallet::::get_fee_currency(account, fee) + } +} + +impl BalanceCmp for Test { + type Error = Error; + + fn cmp_with_precision( + account: &AccountId, + currency: &CurrencyId, + amount: u128, + amount_precision: u32, + ) -> Result { + Pallet::::cmp_with_precision(account, currency, amount, amount_precision) + } +} + pub struct DerivativeAccount; impl DerivativeAccountHandler for DerivativeAccount { fn check_derivative_index_exists( diff --git a/pallets/flexible-fee/src/tests.rs b/pallets/flexible-fee/src/tests.rs index 8ad017fbc..b15c3eef6 100644 --- a/pallets/flexible-fee/src/tests.rs +++ b/pallets/flexible-fee/src/tests.rs @@ -20,12 +20,8 @@ #![cfg(test)] -use bifrost_primitives::TryConvertFrom; -// use balances::Call as BalancesCall; -use crate::{ - mock::*, BlockNumberFor, BoundedVec, Config, DispatchError::BadOrigin, UserDefaultFeeCurrency, -}; -use bifrost_primitives::{CurrencyId, TokenSymbol}; +use std::cmp::Ordering::{Greater, Less}; + use frame_support::{ assert_noop, assert_ok, dispatch::{GetDispatchInfo, Pays, PostDispatchInfo}, @@ -37,16 +33,27 @@ use pallet_transaction_payment::OnChargeTransaction; use sp_runtime::{testing::TestXt, AccountId32}; use zenlink_protocol::AssetId; +use bifrost_primitives::{ + currency::WETH, AccountFeeCurrency, BalanceCmp, CurrencyId, TokenSymbol, TryConvertFrom, BNC, + DOT, KSM, VDOT, +}; + +// use balances::Call as BalancesCall; +use crate::{ + mock::*, BlockNumberFor, BoundedVec, Config, DispatchError::BadOrigin, UserDefaultFeeCurrency, +}; + // some common variables pub const CHARLIE: AccountId32 = AccountId32::new([0u8; 32]); pub const BOB: AccountId32 = AccountId32::new([1u8; 32]); pub const ALICE: AccountId32 = AccountId32::new([2u8; 32]); pub const DICK: AccountId32 = AccountId32::new([3u8; 32]); -pub const CURRENCY_ID_0: CurrencyId = CurrencyId::Native(TokenSymbol::BNC); +pub const CURRENCY_ID_0: CurrencyId = BNC; pub const CURRENCY_ID_1: CurrencyId = CurrencyId::Stable(TokenSymbol::KUSD); -pub const CURRENCY_ID_2: CurrencyId = CurrencyId::Token(TokenSymbol::DOT); -pub const CURRENCY_ID_3: CurrencyId = CurrencyId::VToken(TokenSymbol::DOT); -pub const CURRENCY_ID_4: CurrencyId = CurrencyId::Token(TokenSymbol::KSM); +pub const CURRENCY_ID_2: CurrencyId = DOT; +pub const CURRENCY_ID_3: CurrencyId = VDOT; +pub const CURRENCY_ID_4: CurrencyId = KSM; +pub const CURRENCY_ID_5: CurrencyId = WETH; fn basic_setup() { // Deposit some money in Alice, Bob and Charlie's accounts. @@ -551,3 +558,248 @@ fn get_currency_asset_id_should_work() { assert_eq!(asset_id, ksm_asset_id); }); } + +#[test] +fn get_fee_currency_should_work_with_default_currency() { + new_test_ext().execute_with(|| { + let origin_signed_alice = RuntimeOrigin::signed(ALICE); + assert_ok!(FlexibleFee::set_user_default_fee_currency( + origin_signed_alice.clone(), + Some(CURRENCY_ID_0) + )); + + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 100u128.pow(12))); // BNC + assert_ok!(Currencies::deposit(CURRENCY_ID_1, &ALICE, 100u128.pow(18))); // KUSD CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 100u128.pow(10))); // DOT + assert_ok!(Currencies::deposit(CURRENCY_ID_3, &ALICE, 100u128.pow(10))); // vDOT + assert_ok!(Currencies::deposit(CURRENCY_ID_4, &ALICE, 100u128.pow(12))); // KSM CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 100u128.pow(18))); // ETH + + let currency = >::get_fee_currency( + &ALICE, + 10u128.pow(18).into(), + ) + .unwrap(); + assert_eq!(currency, BNC); + }); +} + +#[test] +fn get_fee_currency_should_work_with_default_currency_poor() { + new_test_ext().execute_with(|| { + let origin_signed_alice = RuntimeOrigin::signed(ALICE); + assert_ok!(FlexibleFee::set_user_default_fee_currency( + origin_signed_alice.clone(), + Some(CURRENCY_ID_0) + )); + + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 1u128.pow(12))); // BNC + assert_ok!(Currencies::deposit(CURRENCY_ID_1, &ALICE, 100u128.pow(18))); // KUSD CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 100u128.pow(10))); // DOT + assert_ok!(Currencies::deposit(CURRENCY_ID_3, &ALICE, 100u128.pow(10))); // vDOT + assert_ok!(Currencies::deposit(CURRENCY_ID_4, &ALICE, 100u128.pow(12))); // KSM CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 100u128.pow(18))); // ETH + + let currency = >::get_fee_currency( + &ALICE, + 10u128.pow(18).into(), + ) + .unwrap(); + assert_eq!(currency, WETH); + }); +} + +#[test] +fn get_fee_currency_should_work_with_weth() { + new_test_ext().execute_with(|| { + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 100u128.pow(12))); // BNC + assert_ok!(Currencies::deposit(CURRENCY_ID_1, &ALICE, 100u128.pow(18))); // KUSD CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 100u128.pow(10))); // DOT + assert_ok!(Currencies::deposit(CURRENCY_ID_3, &ALICE, 100u128.pow(10))); // vDOT + assert_ok!(Currencies::deposit(CURRENCY_ID_4, &ALICE, 100u128.pow(12))); // KSM CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 100u128.pow(18))); // ETH + + let currency = >::get_fee_currency( + &ALICE, + 10u128.pow(18).into(), + ) + .unwrap(); + assert_eq!(currency, WETH); + }); +} + +#[test] +fn get_fee_currency_should_work_with_weth_poor() { + new_test_ext().execute_with(|| { + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 100u128.pow(12))); // BNC + assert_ok!(Currencies::deposit(CURRENCY_ID_1, &ALICE, 100u128.pow(18))); // KUSD CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 100u128.pow(10))); // DOT + assert_ok!(Currencies::deposit(CURRENCY_ID_3, &ALICE, 100u128.pow(10))); // vDOT + assert_ok!(Currencies::deposit(CURRENCY_ID_4, &ALICE, 100u128.pow(12))); // KSM CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 1u128.pow(18))); // ETH + + let asset_order_list_vec: BoundedVec< + CurrencyId, + ::MaxFeeCurrencyOrderListLen, + > = BoundedVec::try_from(vec![CURRENCY_ID_3, CURRENCY_ID_2, CURRENCY_ID_0]).unwrap(); + + assert_ok!(FlexibleFee::set_universal_fee_currency_order_list( + RuntimeOrigin::root(), + asset_order_list_vec.clone() + )); + + let currency = >::get_fee_currency( + &ALICE, + 10u128.pow(18).into(), + ) + .unwrap(); + assert_eq!(currency, VDOT); + }); +} + +#[test] +fn get_fee_currency_should_work_with_universal_fee_currency() { + new_test_ext().execute_with(|| { + let origin_signed_alice = RuntimeOrigin::signed(ALICE); + assert_ok!(FlexibleFee::set_user_default_fee_currency( + origin_signed_alice.clone(), + Some(CURRENCY_ID_0) + )); + + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 1u128.pow(12))); // BNC + assert_ok!(Currencies::deposit(CURRENCY_ID_1, &ALICE, 100u128.pow(18))); // KUSD CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 100u128.pow(10))); // DOT + assert_ok!(Currencies::deposit(CURRENCY_ID_3, &ALICE, 100u128.pow(10))); // vDOT + assert_ok!(Currencies::deposit(CURRENCY_ID_4, &ALICE, 100u128.pow(12))); // KSM CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 1u128.pow(18))); // ETH + + let asset_order_list_vec: BoundedVec< + CurrencyId, + ::MaxFeeCurrencyOrderListLen, + > = BoundedVec::try_from(vec![CURRENCY_ID_3, CURRENCY_ID_2, CURRENCY_ID_0]).unwrap(); + + assert_ok!(FlexibleFee::set_universal_fee_currency_order_list( + RuntimeOrigin::root(), + asset_order_list_vec.clone() + )); + + let currency = >::get_fee_currency( + &ALICE, + 10u128.pow(18).into(), + ) + .unwrap(); + assert_eq!(currency, VDOT); + }); +} + +#[test] +fn get_fee_currency_should_work_with_universal_fee_currency_poor() { + new_test_ext().execute_with(|| { + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 1u128.pow(12))); // BNC + assert_ok!(Currencies::deposit(CURRENCY_ID_1, &ALICE, 100u128.pow(18))); // KUSD CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 100u128.pow(10))); // DOT + assert_ok!(Currencies::deposit(CURRENCY_ID_3, &ALICE, 1u128.pow(10))); // vDOT + assert_ok!(Currencies::deposit(CURRENCY_ID_4, &ALICE, 100u128.pow(12))); // KSM CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 1u128.pow(18))); // ETH + + let asset_order_list_vec: BoundedVec< + CurrencyId, + ::MaxFeeCurrencyOrderListLen, + > = BoundedVec::try_from(vec![CURRENCY_ID_3, CURRENCY_ID_2, CURRENCY_ID_0]).unwrap(); + + assert_ok!(FlexibleFee::set_universal_fee_currency_order_list( + RuntimeOrigin::root(), + asset_order_list_vec.clone() + )); + + let currency = >::get_fee_currency( + &ALICE, + 10u128.pow(18).into(), + ) + .unwrap(); + assert_eq!(currency, DOT); + }); +} + +#[test] +fn get_fee_currency_should_work_with_all_currency_poor() { + new_test_ext().execute_with(|| { + let origin_signed_alice = RuntimeOrigin::signed(ALICE); + assert_ok!(FlexibleFee::set_user_default_fee_currency( + origin_signed_alice.clone(), + Some(CURRENCY_ID_0) + )); + + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 7u128.pow(12))); // BNC + assert_ok!(Currencies::deposit(CURRENCY_ID_1, &ALICE, 6u128.pow(18))); // KUSD CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 5u128.pow(10))); // DOT + assert_ok!(Currencies::deposit(CURRENCY_ID_3, &ALICE, 4u128.pow(10))); // vDOT + assert_ok!(Currencies::deposit(CURRENCY_ID_4, &ALICE, 3u128.pow(12))); // KSM CurrencyNotSupport + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 2u128.pow(18))); // ETH + + let asset_order_list_vec: BoundedVec< + CurrencyId, + ::MaxFeeCurrencyOrderListLen, + > = BoundedVec::try_from(vec![CURRENCY_ID_3, CURRENCY_ID_2, CURRENCY_ID_0]).unwrap(); + + assert_ok!(FlexibleFee::set_universal_fee_currency_order_list( + RuntimeOrigin::root(), + asset_order_list_vec.clone() + )); + + let currency = >::get_fee_currency( + &ALICE, + 10u128.pow(18).into(), + ) + .unwrap(); + assert_eq!(currency, BNC); + }); +} + +#[test] +fn cmp_with_precision_should_work_with_weth() { + new_test_ext().execute_with(|| { + assert_ok!(Currencies::deposit(CURRENCY_ID_5, &ALICE, 10u128.pow(18) - 1)); // ETH + + let ordering = >::cmp_with_precision( + &ALICE, + &WETH, + 10u128.pow(18), + 18u32, + ) + .unwrap(); + assert_eq!(ordering, Less); + }); +} + +#[test] +fn cmp_with_precision_should_work_with_dot() { + new_test_ext().execute_with(|| { + assert_ok!(Currencies::deposit(CURRENCY_ID_2, &ALICE, 10u128.pow(11) + 1)); // DOT + + let ordering = >::cmp_with_precision( + &ALICE, + &DOT, + 10u128.pow(18), + 18u32, + ) + .unwrap(); + assert_eq!(ordering, Greater); + }); +} + +#[test] +fn cmp_with_precision_should_work_with_bnc() { + new_test_ext().execute_with(|| { + assert_ok!(Currencies::deposit(CURRENCY_ID_0, &ALICE, 11u128.pow(12))); // BNC + + let ordering = >::cmp_with_precision( + &ALICE, + &BNC, + 10u128.pow(18), + 18u32, + ) + .unwrap(); + assert_eq!(ordering, Greater); + }); +} diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 87a9d5727..2d6443897 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -26,13 +26,14 @@ use crate::{ }; use frame_support::pallet_prelude::{DispatchResultWithPostInfo, Weight}; use parity_scale_codec::{Decode, Encode, FullCodec}; +use sp_core::U256; use sp_runtime::{ traits::{ AccountIdConversion, AtLeast32BitUnsigned, ConstU32, MaybeSerializeDeserialize, Zero, }, BoundedVec, DispatchError, DispatchResult, TokenError, TypeId, }; -use sp_std::{fmt::Debug, vec::Vec}; +use sp_std::{cmp::Ordering, fmt::Debug, vec::Vec}; pub trait TokenInfo { fn name(&self) -> Option<&str>; @@ -554,13 +555,26 @@ impl SlpHostingFeeProvider { - fn get(a: &AccountId) -> CurrencyId; + type Error; + /// Retrieves the currency used to pay the transaction fee. + /// + /// This method returns the `CurrencyId` of the currency that will be used to pay the + /// transaction fee for the current transaction. It is useful for determining which currency + /// will be deducted to cover the cost of the transaction. + fn get_fee_currency(account: &AccountId, fee: U256) -> Result; } /// Provides account's balance of fee asset currency in a given currency pub trait AccountFeeCurrencyBalanceInCurrency { type Output; - fn get_balance_in_currency(to_currency: CurrencyId, account: &AccountId) -> Self::Output; + type Error; + + // This `fee` variable is used to determine the currency for paying transaction fees. + fn get_balance_in_currency( + to_currency: CurrencyId, + account: &AccountId, + fee: U256, + ) -> Result; } pub trait PriceProvider { @@ -568,3 +582,27 @@ pub trait PriceProvider { fn get_price(asset_a: CurrencyId, asset_b: CurrencyId) -> Option; } + +/// A trait for comparing the balance of a specific currency for a given account. +pub trait BalanceCmp { + type Error; + /// Compares the balance of the specified currency for the given account with + /// an input amount, considering the precision of both the currency and the amount. + /// + /// # Parameters + /// - `account`: The account ID whose balance is to be compared. + /// - `currency`: The currency ID whose balance is to be compared. + /// - `amount`: The amount to compare against. + /// - `amount_precision`: The precision of the input amount. + /// + /// # Returns + /// - `Ok(std::cmp::Ordering)`: The result of the comparison, indicating whether the balance is + /// less than, equal to, or greater than the input amount. + /// - `Err(Self::Error)`: An error if the comparison fails. + fn cmp_with_precision( + account: &AccountId, + currency: &CurrencyId, + amount: u128, + amount_precision: u32, + ) -> Result; +} diff --git a/runtime/bifrost-polkadot/src/evm/evm_fee.rs b/runtime/bifrost-polkadot/src/evm/evm_fee.rs index ee4ecae00..dcb1d0056 100644 --- a/runtime/bifrost-polkadot/src/evm/evm_fee.rs +++ b/runtime/bifrost-polkadot/src/evm/evm_fee.rs @@ -94,7 +94,9 @@ where return Ok(None); } let account_id = T::AddressMapping::into_account_id(*who); - let fee_currency = AC::get(&account_id); + + let fee_currency = + AC::get_fee_currency(&account_id, fee).map_err(|_| Error::::BalanceLow)?; let Some((converted, price)) = C::convert((EC::get(), fee_currency, fee.unique_saturated_into())) diff --git a/runtime/bifrost-polkadot/src/evm/runner.rs b/runtime/bifrost-polkadot/src/evm/runner.rs index 4ad5c4b7f..4e1e1058c 100644 --- a/runtime/bifrost-polkadot/src/evm/runner.rs +++ b/runtime/bifrost-polkadot/src/evm/runner.rs @@ -64,7 +64,12 @@ where let evm_currency = WethAssetId::get(); let account_id = T::AddressMapping::into_account_id(source); let account_nonce = frame_system::Pallet::::account_nonce(&account_id); - let (balance, b_weight) = B::get_balance_in_currency(evm_currency, &account_id); + + let (balance, b_weight) = B::get_balance_in_currency(evm_currency, &account_id, base_fee) + .map_err(|_| RunnerError { + error: R::Error::from(TransactionValidationError::BalanceTooLow), + weight, + })?; let (source_account, inner_weight) = ( Account { diff --git a/runtime/common/src/price.rs b/runtime/common/src/price.rs index 9b0747d18..cbdbc3483 100644 --- a/runtime/common/src/price.rs +++ b/runtime/common/src/price.rs @@ -20,7 +20,10 @@ use frame_support::{ pallet_prelude::Get, traits::tokens::{Fortitude, Preservation}, }; -use sp_runtime::{helpers_128bit::multiply_by_rational_with_rounding, traits::Convert, Rounding}; +use sp_core::U256; +use sp_runtime::{ + helpers_128bit::multiply_by_rational_with_rounding, traits::Convert, DispatchError, Rounding, +}; use sp_std::marker::PhantomData; use xcm::latest::Weight; @@ -67,21 +70,27 @@ where >, { type Output = (Balance, Weight); - - fn get_balance_in_currency(to_currency: CurrencyId, account: &T::AccountId) -> Self::Output { - let from_currency = AC::get(account); + type Error = DispatchError; + + fn get_balance_in_currency( + to_currency: CurrencyId, + account: &T::AccountId, + fee: U256, + ) -> Result { + let from_currency = AC::get_fee_currency(account, fee) + .map_err(|_| DispatchError::Other("Get Currency Error."))?; let account_balance = I::reducible_balance(from_currency, account, Preservation::Preserve, Fortitude::Polite); let price_weight = T::DbWeight::get().reads(2); // 1 read to get currency and 1 read to get balance if from_currency == to_currency { - return (account_balance, price_weight); + return Ok((account_balance, price_weight)); } let Some((converted, _)) = C::convert((from_currency, to_currency, account_balance)) else { - return (0, price_weight); + return Ok((0, price_weight)); }; - (converted, price_weight) + Ok((converted, price_weight)) } }