Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize flexible-fee pallet #1430

Merged
merged 3 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions pallets/fee-share/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ mod benchmarking;

pub mod weights;

use bifrost_primitives::{CurrencyId, DistributionId, Price};
use bifrost_primitives::{CurrencyId, DistributionId, Price, PriceFeeder};
use frame_support::{
pallet_prelude::*,
sp_runtime::{
Expand All @@ -44,7 +44,6 @@ use frame_support::{
use frame_system::pallet_prelude::*;
use orml_traits::MultiCurrency;
pub use pallet::*;
use pallet_traits::PriceFeeder;
use sp_std::cmp::Ordering;
pub use weights::WeightInfo;

Expand Down
13 changes: 11 additions & 2 deletions pallets/fee-share/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ use orml_traits::{
location::RelativeReserveProvider, parameter_type_with_key, DataFeeder, DataProvider,
DataProviderExtended, MultiCurrency,
};
use pallet_traits::PriceFeeder;
use sp_core::ConstU32;
use sp_runtime::{
traits::{AccountIdConversion, IdentityLookup, UniqueSaturatedInto},
Expand Down Expand Up @@ -279,13 +278,23 @@ impl PriceFeeder for MockPriceFeeder {
todo!()
}

fn get_oracle_amount_by_currency_and_amount_in(
fn get_amount_by_prices(
_currency_in: &CurrencyId,
_amount_in: bifrost_primitives::Balance,
_currency_in_price: Price,
_currency_out: &CurrencyId,
_currency_out_price: Price,
) -> Option<bifrost_primitives::Balance> {
todo!()
}

fn get_oracle_amount_by_currency_and_amount_in(
_currency_in: &CurrencyId,
_amount_in: bifrost_primitives::Balance,
_currency_out: &CurrencyId,
) -> Option<(bifrost_primitives::Balance, Price, Price)> {
todo!()
}
}

pub struct ParaInfo;
Expand Down
2 changes: 0 additions & 2 deletions pallets/flexible-fee/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,11 @@ bifrost-currencies = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
cumulus-primitives-core = { workspace = true }
bifrost-salp = { workspace = true }
bifrost-asset-registry = { workspace = true }
bifrost-xcm-interface = { workspace = true }
xcm-executor = { workspace = true }
xcm-builder = { workspace = true }
pallet-xcm = { workspace = true }
bifrost-vtoken-voting = { workspace = true, features = ["kusama"] }

[features]
default = ["std"]
Expand Down
2 changes: 1 addition & 1 deletion pallets/flexible-fee/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ benchmarks! {
let caller = whitelisted_caller();
}: _(RawOrigin::Signed(caller),Some(CurrencyId::Token(TokenSymbol::DOT)))

set_universal_fee_currency_order_list {
set_default_fee_currency_list {
let default_list = BoundedVec::try_from(vec![CurrencyId::Token(TokenSymbol::DOT)]).unwrap();
}: _(RawOrigin::Root,default_list)

Expand Down
95 changes: 95 additions & 0 deletions pallets/flexible-fee/src/impls/account_fee_currency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// This file is part of Bifrost.

// Copyright (C) Liebi Technologies PTE. LTD.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program 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.

// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{Config, Error, Pallet, UniversalFeeCurrencyOrderList, UserDefaultFeeCurrency};
use bifrost_primitives::{AccountFeeCurrency, BalanceCmp, CurrencyId, WETH};
use frame_support::traits::{
fungibles::Inspect,
tokens::{Fortitude, Preservation},
};
use sp_arithmetic::traits::UniqueSaturatedInto;
use sp_core::U256;
use sp_std::cmp::Ordering;

/// Provides account's fee payment asset or default fee asset ( Native asset )
impl<T: Config> AccountFeeCurrency<T::AccountId> for Pallet<T> {
type Error = Error<T>;

/// 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<CurrencyId, Error<T>> {
let fee: u128 = fee.unique_saturated_into();
let priority_currency = UserDefaultFeeCurrency::<T>::get(account);
let mut currency_list = UniversalFeeCurrencyOrderList::<T>::get();

let first_item_index = 0;
currency_list
.try_insert(first_item_index, WETH)
.map_err(|_| Error::<T>::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::<T>::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);
}
}
20 changes: 20 additions & 0 deletions pallets/flexible-fee/src/impls/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This file is part of Bifrost.

// Copyright (C) Liebi Technologies PTE. LTD.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program 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.

// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.

pub mod account_fee_currency;
pub mod on_charge_transaction;
162 changes: 162 additions & 0 deletions pallets/flexible-fee/src/impls/on_charge_transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// This file is part of Bifrost.

// Copyright (C) Liebi Technologies PTE. LTD.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program 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.

// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{Config, ExtraFeeByCall, Pallet};
use bifrost_primitives::{Balance, CurrencyId, Price, PriceFeeder, BNC};
use orml_traits::MultiCurrency;
use pallet_transaction_payment::OnChargeTransaction;
use parity_scale_codec::Encode;
use sp_core::Get;
use sp_runtime::{
traits::{DispatchInfoOf, PostDispatchInfoOf, Zero},
transaction_validity::{InvalidTransaction, TransactionValidityError},
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PaymentInfo {
Native(Balance),
NonNative(Balance, CurrencyId, Price, Price),
}

/// Default implementation for a Currency and an OnUnbalanced handler.
impl<T> OnChargeTransaction<T> for Pallet<T>
where
T: Config,
T::MultiCurrency: MultiCurrency<T::AccountId, CurrencyId = CurrencyId>,
{
type Balance = Balance;
type LiquidityInfo = Option<PaymentInfo>;

/// Withdraw the predicted fee from the transaction origin.
///
/// Note: The `fee` already includes the `tip`.
fn withdraw_fee(
who: &T::AccountId,
call: &T::RuntimeCall,
_info: &DispatchInfoOf<T::RuntimeCall>,
fee: Self::Balance,
_tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
if fee.is_zero() {
return Ok(None);
}

let (fee_currency, fee_amount, bnc_price, fee_currency_price) =
Self::get_fee_currency_and_fee_amount(who, fee)
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;

// withdraw normal extrinsic fee
T::MultiCurrency::withdraw(fee_currency, who, fee_amount)
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;

for (call_name, (extra_fee_currency, extra_fee_amount, extra_fee_receiver)) in
ExtraFeeByCall::<T>::iter()
{
let raw_call_name = call_name.to_vec();
let raw_call_name_len = raw_call_name.len();
if call.encode().len() >= raw_call_name_len {
if call.encode()[0..raw_call_name_len].eq(&raw_call_name) {
match Self::charge_extra_fee(
who,
extra_fee_currency,
extra_fee_amount,
&extra_fee_receiver,
) {
Ok(_) => {},
Err(_) => {
return Err(TransactionValidityError::Invalid(
InvalidTransaction::Payment,
));
},
}
};
}
}

if fee_currency == BNC {
Ok(Some(PaymentInfo::Native(fee_amount)))
} else {
Ok(Some(PaymentInfo::NonNative(
fee_amount,
fee_currency,
bnc_price,
fee_currency_price,
)))
}
}

/// Hand the fee and the tip over to the `[OnUnbalanced]` implementation.
/// Since the predicted fee might have been too high, parts of the fee may
/// be refunded.
///
/// Note: The `fee` already includes the `tip`.
fn correct_and_deposit_fee(
who: &T::AccountId,
_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
corrected_fee: Self::Balance,
tip: Self::Balance,
already_withdrawn: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError> {
if let Some(paid) = already_withdrawn {
// Calculate how much refund we should return
let (currency, refund, fee, tip) = match paid {
PaymentInfo::Native(paid_fee) => (
BNC,
paid_fee.saturating_sub(corrected_fee),
corrected_fee.saturating_sub(tip),
tip,
),
PaymentInfo::NonNative(paid_fee, fee_currency, bnc_price, fee_currency_price) => {
// calculate corrected_fee in the non-native currency
let converted_corrected_fee = T::PriceFeeder::get_amount_by_prices(
&BNC,
corrected_fee,
bnc_price,
&fee_currency,
fee_currency_price,
)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
let refund = paid_fee.saturating_sub(converted_corrected_fee);
let converted_tip = T::PriceFeeder::get_amount_by_prices(
&BNC,
tip,
bnc_price,
&fee_currency,
fee_currency_price,
)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
(
fee_currency,
refund,
converted_corrected_fee.saturating_sub(converted_tip),
converted_tip,
)
},
};
// refund to the account that paid the fees
T::MultiCurrency::deposit(currency, who, refund)
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;

// deposit the fee
T::MultiCurrency::deposit(currency, &T::TreasuryAccount::get(), fee + tip)
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
}
Ok(())
}
}
Loading