Skip to content
This repository was archived by the owner on May 30, 2023. It is now read-only.
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
4 changes: 2 additions & 2 deletions transaction-multi-payment/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-transaction-multi-payment"
version = "7.1.0"
version = "8.0.0"
description = "Transaction multi currency payment support module"
authors = ["GalacticCoucil"]
edition = "2021"
Expand All @@ -18,7 +18,7 @@ scale-info = { version = "1.0", default-features = false, features = ["derive"]
orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "aac79b3b31953381669a2ffa9b3e9bfe48e87f38", default-features = false }

# HydraDX traits
hydradx-traits = { path="../traits", default-features = false }
hydradx-traits = { path = "../traits", default-features = false }

# Substrate dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.17", default-features = false }
Expand Down
245 changes: 173 additions & 72 deletions transaction-multi-payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,16 @@ use frame_support::traits::IsSubType;

use scale_info::TypeInfo;

use crate::traits::{CurrencyWithdraw, PaymentWithdrawResult};
pub use crate::traits::*;
use frame_support::dispatch::DispatchError;

type AssetIdOf<T> = <<T as Config>::Currencies as MultiCurrency<<T as frame_system::Config>::AccountId>>::CurrencyId;
type BalanceOf<T> = <<T as Config>::Currencies as MultiCurrency<<T as frame_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<C, T> = <C as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;

/// Spot price type
pub type Price = FixedU128;

type NegativeImbalanceOf<C, T> = <C as Currency<<T as frame_system::Config>::AccountId>>::NegativeImbalance;

// Re-export pallet items so that they can be accessed from the crate namespace.
pub use pallet::*;

Expand Down Expand Up @@ -137,6 +136,10 @@ pub mod pallet {
/// Native Asset
#[pallet::constant]
type NativeAssetId: Get<AssetIdOf<Self>>;

/// Account where fees are deposited
#[pallet::constant]
type FeeReceiver: Get<Self::AccountId>;
}

#[pallet::event]
Expand Down Expand Up @@ -190,35 +193,26 @@ pub mod pallet {

/// Math overflow
Overflow,

/// Fallback account is not set.
FallbackAccountNotSet,
}

/// Account currency map
#[pallet::storage]
#[pallet::getter(fn get_currency)]
pub type AccountCurrencyMap<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, AssetIdOf<T>, OptionQuery>;

/// Curated list of currencies which fees can be paid with
/// Curated list of currencies which fees can be paid mapped to corresponding fallback price
#[pallet::storage]
#[pallet::getter(fn currencies)]
pub type AcceptedCurrencies<T: Config> = StorageMap<_, Twox64Concat, AssetIdOf<T>, Price, OptionQuery>;

/// Block storage for accepted currency price
/// Asset prices from the spot price provider or the fallback price if the price is not available. Updated at the beginning of every block.
#[pallet::storage]
#[pallet::getter(fn currency_price)]
pub type AcceptedCurrencyPrice<T: Config> = StorageMap<_, Twox64Concat, AssetIdOf<T>, Price, OptionQuery>;

/// Account to use when pool does not exist.
#[pallet::storage]
#[pallet::getter(fn fallback_account)]
pub type FallbackAccount<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for migration to delete the storage ? only if upgraded after it is already in use.


#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub currencies: Vec<(AssetIdOf<T>, Price)>,
pub fallback_account: Option<T::AccountId>,
pub account_currencies: Vec<(T::AccountId, AssetIdOf<T>)>,
}

Expand All @@ -227,7 +221,6 @@ pub mod pallet {
fn default() -> Self {
GenesisConfig {
currencies: vec![],
fallback_account: None,
account_currencies: vec![],
}
}
Expand All @@ -236,12 +229,6 @@ pub mod pallet {
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
if self.fallback_account == None {
panic!("Fallback account is not set");
}

FallbackAccount::<T>::put(self.fallback_account.clone().expect("Fallback account is not set"));

for (asset, price) in &self.currencies {
AcceptedCurrencies::<T>::insert(asset, price);
}
Expand Down Expand Up @@ -279,7 +266,7 @@ pub mod pallet {
<AccountCurrencyMap<T>>::insert(who.clone(), currency);

if T::WithdrawFeeForSetCurrency::get() == Pays::Yes {
Self::withdraw_set_fee(&who)?;
Self::transfer_set_fee(&who)?;
}

Self::deposit_event(Event::CurrencySet {
Expand Down Expand Up @@ -352,15 +339,52 @@ where
Pallet::<T>::get_currency(who).unwrap_or_else(T::NativeAssetId::get)
}

/// Execute a trade to buy HDX and sell selected currency.
pub fn withdraw_fee_non_native(
who: &T::AccountId,
fee: BalanceOf<T>,
) -> Result<PaymentWithdrawResult, DispatchError> {
let currency = Self::account_currency(who);
/// Transfer fee without executing an AMM trade
pub fn transfer_set_fee(who: &T::AccountId) -> DispatchResult {
let base_fee = Self::weight_to_fee(T::BlockWeights::get().get(DispatchClass::Normal).base_extrinsic);
let adjusted_weight_fee = Self::weight_to_fee(T::WeightInfo::set_currency());
let fee = base_fee.saturating_add(adjusted_weight_fee);
let (currency, maybe_price) = Self::get_currency_and_price(who)?;

let amount = match maybe_price {
None => fee,
Some(price) => price.checked_mul_int(fee).ok_or(Error::<T>::Overflow)?,
};

T::Currencies::transfer(currency, who, &T::FeeReceiver::get(), amount)?;

Self::deposit_event(Event::FeeWithdrawn {
account_id: who.clone(),
asset_id: currency,
native_fee_amount: fee,
non_native_fee_amount: amount,
destination_account_id: T::FeeReceiver::get(),
});

Ok(())
}

fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
// cap the weight to the maximum defined in runtime, otherwise it will be the
// `Bounded` maximum of its data type, which is not desired.
let capped_weight: Weight = weight.min(T::BlockWeights::get().max_block);
<T as Config>::WeightToFee::calc(&capped_weight)
}

fn check_balance(account: &T::AccountId, currency: AssetIdOf<T>) -> Result<(), Error<T>> {
if T::Currencies::free_balance(currency, account) == BalanceOf::<T>::zero() {
return Err(Error::<T>::ZeroBalance);
};
Ok(())
}

fn get_currency_and_price(
who: &<T as frame_system::Config>::AccountId,
) -> Result<(AssetIdOf<T>, Option<Price>), DispatchError> {
let native_currency = T::NativeAssetId::get();
let currency = Self::account_currency(who);
if currency == T::NativeAssetId::get() {
Ok(PaymentWithdrawResult::Native)
Ok((native_currency, None))
} else {
let price = if let Some(spot_price) = Self::currency_price(currency) {
spot_price
Expand All @@ -374,67 +398,144 @@ where
}
};

let amount = price.checked_mul_int(fee).ok_or(Error::<T>::Overflow)?;

T::Currencies::transfer(
currency,
who,
&Self::fallback_account().ok_or(Error::<T>::FallbackAccountNotSet)?,
amount,
)?;

Self::deposit_event(Event::FeeWithdrawn {
account_id: who.clone(),
asset_id: currency,
native_fee_amount: fee,
non_native_fee_amount: amount,
destination_account_id: Self::fallback_account().ok_or(Error::<T>::FallbackAccountNotSet)?,
});

Ok(PaymentWithdrawResult::Transferred)
Ok((currency, Some(price)))
}
}
}

pub fn withdraw_set_fee(who: &T::AccountId) -> DispatchResult {
let base_fee = Self::weight_to_fee(T::BlockWeights::get().get(DispatchClass::Normal).base_extrinsic);
let adjusted_weight_fee = Self::weight_to_fee(T::WeightInfo::set_currency());
let fee = base_fee.saturating_add(adjusted_weight_fee);

let result = Self::withdraw(who, fee)?;
match result {
PaymentWithdrawResult::Transferred => Ok(()),
PaymentWithdrawResult::Native => T::Currencies::withdraw(T::NativeAssetId::get(), who, fee),
}
impl<T: Config> TransactionMultiPaymentDataProvider<<T as frame_system::Config>::AccountId, AssetIdOf<T>, Price>
for Pallet<T>
where
BalanceOf<T>: FixedPointOperand,
{
fn get_currency_and_price(
who: &<T as frame_system::Config>::AccountId,
) -> Result<(AssetIdOf<T>, Option<Price>), DispatchError> {
Self::get_currency_and_price(who)
}

fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
// cap the weight to the maximum defined in runtime, otherwise it will be the
// `Bounded` maximum of its data type, which is not desired.
let capped_weight: Weight = weight.min(T::BlockWeights::get().max_block);
<T as Config>::WeightToFee::calc(&capped_weight)
fn get_fee_receiver() -> <T as frame_system::Config>::AccountId {
T::FeeReceiver::get()
}
}

fn check_balance(account: &T::AccountId, currency: AssetIdOf<T>) -> Result<(), Error<T>> {
if T::Currencies::free_balance(currency, account) == BalanceOf::<T>::zero() {
return Err(Error::<T>::ZeroBalance);
};
/// Deposits all fees to some account
pub struct DepositAll<T>(PhantomData<T>);

impl<T: Config> DepositFee<T::AccountId, AssetIdOf<T>, BalanceOf<T>> for DepositAll<T> {
fn deposit_fee(who: &T::AccountId, currency: AssetIdOf<T>, amount: BalanceOf<T>) -> DispatchResult {
<T as Config>::Currencies::deposit(currency, who, amount)?;
Ok(())
}
}

impl<T: Config> CurrencyWithdraw<<T as frame_system::Config>::AccountId, BalanceOf<T>> for Pallet<T>
/// Implements the transaction payment for native as well as non-native currencies
pub struct TransferFees<MC, DP, DF>(PhantomData<(MC, DP, DF)>);

impl<T, MC, DP, DF> OnChargeTransaction<T> for TransferFees<MC, DP, DF>
where
BalanceOf<T>: FixedPointOperand,
T: Config,
MC: MultiCurrency<<T as frame_system::Config>::AccountId>,
AssetIdOf<T>: Into<MC::CurrencyId>,
MC::Balance: FixedPointOperand,
DP: TransactionMultiPaymentDataProvider<T::AccountId, AssetIdOf<T>, Price>,
DF: DepositFee<T::AccountId, MC::CurrencyId, MC::Balance>,
{
fn withdraw(who: &T::AccountId, fee: BalanceOf<T>) -> Result<PaymentWithdrawResult, DispatchError> {
Self::withdraw_fee_non_native(who, fee)
type LiquidityInfo = Option<PaymentInfo<Self::Balance, AssetIdOf<T>, Price>>;
type Balance = <MC as MultiCurrency<<T as frame_system::Config>::AccountId>>::Balance;

/// Withdraw the predicted fee from the transaction origin.
///
/// Note: The `fee` already includes the `tip`.
fn withdraw_fee(
who: &T::AccountId,
_call: &T::Call,
_info: &DispatchInfoOf<T::Call>,
fee: Self::Balance,
_tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
if fee.is_zero() {
return Ok(None);
}
// get the currency in which fees are paid. In case of non-native currency, the price is required to calculate final fee.
let currency_data = DP::get_currency_and_price(who)
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;

match currency_data {
(_, None) => match MC::withdraw(T::NativeAssetId::get().into(), who, fee) {
Ok(()) => Ok(Some(PaymentInfo::Native(fee))),
Err(_) => Err(InvalidTransaction::Payment.into()),
},
(currency, Some(price)) => {
let converted_fee = price
.checked_mul_int(fee)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
match MC::withdraw(currency.into(), who, converted_fee) {
Ok(()) => Ok(Some(PaymentInfo::NonNative(converted_fee, currency, price))),
Err(_) => Err(InvalidTransaction::Payment.into()),
}
}
}
}

/// 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::Call>,
_post_info: &PostDispatchInfoOf<T::Call>,
corrected_fee: Self::Balance,
tip: Self::Balance,
already_withdrawn: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError> {
let fee_receiver = DP::get_fee_receiver();

if let Some(paid) = already_withdrawn {
// Calculate how much refund we should return
let (currency, refund, fee, tip) = match paid {
PaymentInfo::Native(paid_fee) => (
T::NativeAssetId::get().into(),
paid_fee.saturating_sub(corrected_fee),
corrected_fee.saturating_sub(tip),
tip,
),
PaymentInfo::NonNative(paid_fee, currency, price) => {
// calculate corrected_fee in the non-native currency
let converted_corrected_fee = price
.checked_mul_int(corrected_fee)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
let refund = paid_fee.saturating_sub(converted_corrected_fee);
let converted_tip = price
.checked_mul_int(tip)
.ok_or(TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
(
currency.into(),
refund,
converted_corrected_fee.saturating_sub(converted_tip),
converted_tip,
)
}
};

// refund to the account that paid the fees
MC::deposit(currency, who, refund)
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;

// deposit the fee
DF::deposit_fee(&fee_receiver, currency, fee + tip)
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
}

Ok(())
}
}

/// Implements the transaction payment for native as well as non-native currencies
pub struct MultiCurrencyAdapter<C, OU, SW>(PhantomData<(C, OU, SW)>);
pub struct WithdrawFees<C, OU, SW>(PhantomData<(C, OU, SW)>);

impl<T, C, OU, SW> OnChargeTransaction<T> for MultiCurrencyAdapter<C, OU, SW>
impl<T, C, OU, SW> OnChargeTransaction<T> for WithdrawFees<C, OU, SW>
where
T: Config,
T::TransactionByteFee: Get<<C as Currency<<T as frame_system::Config>::AccountId>>::Balance>,
Expand Down
Loading