mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
decouple transaction payment and currency (#6912)
* wip: setup types * fix types * make tx payment pallet independent from balances * fix dependent tests * comments * restructure a bit and include more info * clean up ugly phantom * reduce complexity * minor doc improvements * use shorthand * doc * fix line lenght and style * readd BalanceOf * some clarifications and readability improvements * move balance type to OnChargeTransaction * remove noise * fix style * Apply suggestions from code review improved documentation Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Improve naming and documentation Apply suggestions from code review Co-authored-by: Alexander Theißen <alex.theissen@me.com> * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * always call withdraw_fee * move NegativeImbalanceOf to payment module * fix unused import Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -37,6 +37,7 @@ pub use frame_support::{
|
||||
constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},
|
||||
},
|
||||
};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
|
||||
/// Import the template pallet.
|
||||
pub use template;
|
||||
@@ -249,8 +250,7 @@ parameter_types! {
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type OnTransactionPayment = ();
|
||||
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
type WeightToFee = IdentityFee<Balance>;
|
||||
type FeeMultiplierUpdate = ();
|
||||
|
||||
@@ -64,7 +64,7 @@ use pallet_grandpa::fg_primitives;
|
||||
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
|
||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
|
||||
pub use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
|
||||
pub use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment, CurrencyAdapter};
|
||||
use pallet_session::{historical as pallet_session_historical};
|
||||
use sp_inherents::{InherentData, CheckInherentsResult};
|
||||
use static_assertions::const_assert;
|
||||
@@ -360,8 +360,7 @@ parameter_types! {
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type OnTransactionPayment = DealWithFees;
|
||||
type OnChargeTransaction = CurrencyAdapter<Balances, DealWithFees>;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
type WeightToFee = IdentityFee<Balance>;
|
||||
type FeeMultiplierUpdate =
|
||||
|
||||
@@ -29,6 +29,7 @@ use sp_io;
|
||||
use frame_support::{impl_outer_origin, impl_outer_event, parameter_types};
|
||||
use frame_support::traits::Get;
|
||||
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use std::cell::RefCell;
|
||||
use crate::{GenesisConfig, Module, Trait, decl_tests, tests::CallWithDispatchInfo};
|
||||
|
||||
@@ -97,8 +98,7 @@ parameter_types! {
|
||||
pub const TransactionByteFee: u64 = 1;
|
||||
}
|
||||
impl pallet_transaction_payment::Trait for Test {
|
||||
type Currency = Module<Test>;
|
||||
type OnTransactionPayment = ();
|
||||
type OnChargeTransaction = CurrencyAdapter<Module<Test>, ()>;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
type WeightToFee = IdentityFee<u64>;
|
||||
type FeeMultiplierUpdate = ();
|
||||
|
||||
@@ -31,6 +31,7 @@ use frame_support::traits::{Get, StorageMapShim};
|
||||
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
|
||||
use std::cell::RefCell;
|
||||
use crate::{GenesisConfig, Module, Trait, decl_tests, tests::CallWithDispatchInfo};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
|
||||
use frame_system as system;
|
||||
impl_outer_origin!{
|
||||
@@ -97,8 +98,7 @@ parameter_types! {
|
||||
pub const TransactionByteFee: u64 = 1;
|
||||
}
|
||||
impl pallet_transaction_payment::Trait for Test {
|
||||
type Currency = Module<Test>;
|
||||
type OnTransactionPayment = ();
|
||||
type OnChargeTransaction = CurrencyAdapter<Module<Test>, ()>;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
type WeightToFee = IdentityFee<u64>;
|
||||
type FeeMultiplierUpdate = ();
|
||||
|
||||
@@ -491,6 +491,7 @@ mod tests {
|
||||
traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons},
|
||||
};
|
||||
use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo};
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
use pallet_balances::Call as BalancesCall;
|
||||
use hex_literal::hex;
|
||||
const TEST_KEY: &[u8] = &*b":test:key:";
|
||||
@@ -632,8 +633,7 @@ mod tests {
|
||||
pub const TransactionByteFee: Balance = 0;
|
||||
}
|
||||
impl pallet_transaction_payment::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type OnTransactionPayment = ();
|
||||
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
type WeightToFee = IdentityFee<Balance>;
|
||||
type FeeMultiplierUpdate = ();
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
//! - A means of updating the fee for the next block, via defining a multiplier, based on the
|
||||
//! final state of the chain at the end of the previous block. This can be configured via
|
||||
//! [`Trait::FeeMultiplierUpdate`]
|
||||
//! - How the fees are paid via [`Trait::OnChargeTransaction`].
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
@@ -36,7 +37,7 @@ use sp_std::prelude::*;
|
||||
use codec::{Encode, Decode};
|
||||
use frame_support::{
|
||||
decl_storage, decl_module,
|
||||
traits::{Currency, Get, OnUnbalanced, ExistenceRequirement, WithdrawReasons, Imbalance},
|
||||
traits::Get,
|
||||
weights::{
|
||||
Weight, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Pays, WeightToFeePolynomial,
|
||||
WeightToFeeCoefficient,
|
||||
@@ -46,23 +47,23 @@ use frame_support::{
|
||||
use sp_runtime::{
|
||||
FixedU128, FixedPointNumber, FixedPointOperand, Perquintill, RuntimeDebug,
|
||||
transaction_validity::{
|
||||
TransactionPriority, ValidTransaction, InvalidTransaction, TransactionValidityError,
|
||||
TransactionValidity,
|
||||
TransactionPriority, ValidTransaction, TransactionValidityError, TransactionValidity,
|
||||
},
|
||||
traits::{
|
||||
Zero, Saturating, SignedExtension, SaturatedConversion, Convert, Dispatchable,
|
||||
Saturating, SignedExtension, SaturatedConversion, Convert, Dispatchable,
|
||||
DispatchInfoOf, PostDispatchInfoOf,
|
||||
},
|
||||
};
|
||||
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
|
||||
|
||||
mod payment;
|
||||
pub use payment::*;
|
||||
|
||||
/// Fee multiplier.
|
||||
pub type Multiplier = FixedU128;
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance;
|
||||
type NegativeImbalanceOf<T> =
|
||||
<<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
|
||||
<<T as Trait>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;
|
||||
|
||||
/// A struct to update the weight multiplier per block. It implements `Convert<Multiplier,
|
||||
/// Multiplier>`, meaning that it can convert the previous multiplier to the next one. This should
|
||||
@@ -213,13 +214,13 @@ impl Default for Releases {
|
||||
}
|
||||
|
||||
pub trait Trait: frame_system::Trait {
|
||||
/// The currency type in which fees will be paid.
|
||||
type Currency: Currency<Self::AccountId> + Send + Sync;
|
||||
|
||||
/// Handler for the unbalanced reduction when taking transaction fees. This is either one or
|
||||
/// two separate imbalances, the first is the transaction fee paid, the second is the tip paid,
|
||||
/// if any.
|
||||
type OnTransactionPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
||||
/// Handler for withdrawing, refunding and depositing the transaction fee.
|
||||
/// Transaction fees are withdrawn before the transaction is executed.
|
||||
/// After the transaction was executed the transaction weight can be
|
||||
/// adjusted, depending on the used resources by the transaction. If the
|
||||
/// transaction weight is lower than expected, parts of the transaction fee
|
||||
/// might be refunded. In the end the fees can be deposited.
|
||||
type OnChargeTransaction: OnChargeTransaction<Self>;
|
||||
|
||||
/// The fee to be paid for making a transaction; the per-byte portion.
|
||||
type TransactionByteFee: Get<BalanceOf<Self>>;
|
||||
@@ -442,30 +443,21 @@ impl<T: Trait + Send + Sync> ChargeTransactionPayment<T> where
|
||||
fn withdraw_fee(
|
||||
&self,
|
||||
who: &T::AccountId,
|
||||
call: &T::Call,
|
||||
info: &DispatchInfoOf<T::Call>,
|
||||
len: usize,
|
||||
) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {
|
||||
) -> Result<
|
||||
(
|
||||
BalanceOf<T>,
|
||||
<<T as Trait>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo,
|
||||
),
|
||||
TransactionValidityError,
|
||||
> {
|
||||
let tip = self.0;
|
||||
let fee = Module::<T>::compute_fee(len as u32, info, tip);
|
||||
|
||||
// Only mess with balances if fee is not zero.
|
||||
if fee.is_zero() {
|
||||
return Ok((fee, None));
|
||||
}
|
||||
|
||||
match T::Currency::withdraw(
|
||||
who,
|
||||
fee,
|
||||
if tip.is_zero() {
|
||||
WithdrawReasons::TRANSACTION_PAYMENT
|
||||
} else {
|
||||
WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
|
||||
},
|
||||
ExistenceRequirement::KeepAlive,
|
||||
) {
|
||||
Ok(imbalance) => Ok((fee, Some(imbalance))),
|
||||
Err(_) => Err(InvalidTransaction::Payment.into()),
|
||||
}
|
||||
<<T as Trait>::OnChargeTransaction as OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)
|
||||
.map(|i| (fee, i))
|
||||
}
|
||||
|
||||
/// Get an appropriate priority for a transaction with the given length and info.
|
||||
@@ -505,17 +497,24 @@ impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> whe
|
||||
type AccountId = T::AccountId;
|
||||
type Call = T::Call;
|
||||
type AdditionalSigned = ();
|
||||
type Pre = (BalanceOf<T>, Self::AccountId, Option<NegativeImbalanceOf<T>>, BalanceOf<T>);
|
||||
type Pre = (
|
||||
// tip
|
||||
BalanceOf<T>,
|
||||
// who paid the fee
|
||||
Self::AccountId,
|
||||
// imbalance resulting from withdrawing the fee
|
||||
<<T as Trait>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo,
|
||||
);
|
||||
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) }
|
||||
|
||||
fn validate(
|
||||
&self,
|
||||
who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
call: &Self::Call,
|
||||
info: &DispatchInfoOf<Self::Call>,
|
||||
len: usize,
|
||||
) -> TransactionValidity {
|
||||
let (fee, _) = self.withdraw_fee(who, info, len)?;
|
||||
let (fee, _) = self.withdraw_fee(who, call, info, len)?;
|
||||
Ok(ValidTransaction {
|
||||
priority: Self::get_priority(len, info, fee),
|
||||
..Default::default()
|
||||
@@ -525,12 +524,12 @@ impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> whe
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
who: &Self::AccountId,
|
||||
_call: &Self::Call,
|
||||
call: &Self::Call,
|
||||
info: &DispatchInfoOf<Self::Call>,
|
||||
len: usize
|
||||
) -> Result<Self::Pre, TransactionValidityError> {
|
||||
let (fee, imbalance) = self.withdraw_fee(who, info, len)?;
|
||||
Ok((self.0, who.clone(), imbalance, fee))
|
||||
let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;
|
||||
Ok((self.0, who.clone(), imbalance))
|
||||
}
|
||||
|
||||
fn post_dispatch(
|
||||
@@ -540,32 +539,14 @@ impl<T: Trait + Send + Sync> SignedExtension for ChargeTransactionPayment<T> whe
|
||||
len: usize,
|
||||
_result: &DispatchResult,
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
let (tip, who, imbalance, fee) = pre;
|
||||
if let Some(payed) = imbalance {
|
||||
let actual_fee = Module::<T>::compute_actual_fee(
|
||||
len as u32,
|
||||
info,
|
||||
post_info,
|
||||
tip,
|
||||
);
|
||||
let refund = fee.saturating_sub(actual_fee);
|
||||
let actual_payment = match T::Currency::deposit_into_existing(&who, refund) {
|
||||
Ok(refund_imbalance) => {
|
||||
// The refund cannot be larger than the up front payed max weight.
|
||||
// `PostDispatchInfo::calc_unspent` guards against such a case.
|
||||
match payed.offset(refund_imbalance) {
|
||||
Ok(actual_payment) => actual_payment,
|
||||
Err(_) => return Err(InvalidTransaction::Payment.into()),
|
||||
}
|
||||
}
|
||||
// We do not recreate the account using the refund. The up front payment
|
||||
// is gone in that case.
|
||||
Err(_) => payed,
|
||||
};
|
||||
let imbalances = actual_payment.split(tip);
|
||||
T::OnTransactionPayment::on_unbalanceds(Some(imbalances.0).into_iter()
|
||||
.chain(Some(imbalances.1)));
|
||||
}
|
||||
let (tip, who, imbalance) = pre;
|
||||
let actual_fee = Module::<T>::compute_actual_fee(
|
||||
len as u32,
|
||||
info,
|
||||
post_info,
|
||||
tip,
|
||||
);
|
||||
T::OnChargeTransaction::correct_and_deposit_fee(&who, info, post_info, actual_fee, tip, imbalance)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -580,6 +561,7 @@ mod tests {
|
||||
DispatchClass, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Weight,
|
||||
WeightToFeePolynomial, WeightToFeeCoefficients, WeightToFeeCoefficient,
|
||||
},
|
||||
traits::Currency,
|
||||
};
|
||||
use pallet_balances::Call as BalancesCall;
|
||||
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
|
||||
@@ -699,8 +681,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl Trait for Runtime {
|
||||
type Currency = pallet_balances::Module<Runtime>;
|
||||
type OnTransactionPayment = ();
|
||||
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
type WeightToFee = WeightToFee;
|
||||
type FeeMultiplierUpdate = ();
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
///! Traits and default implementation for paying transaction fees.
|
||||
use crate::Trait;
|
||||
use codec::FullCodec;
|
||||
use frame_support::{
|
||||
traits::{Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, WithdrawReasons},
|
||||
unsigned::TransactionValidityError,
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::{AtLeast32BitUnsigned, DispatchInfoOf, MaybeSerializeDeserialize, PostDispatchInfoOf, Saturating, Zero},
|
||||
transaction_validity::InvalidTransaction,
|
||||
};
|
||||
use sp_std::{fmt::Debug, marker::PhantomData};
|
||||
|
||||
type NegativeImbalanceOf<C, T> =
|
||||
<C as Currency<<T as frame_system::Trait>::AccountId>>::NegativeImbalance;
|
||||
|
||||
/// Handle withdrawing, refunding and depositing of transaction fees.
|
||||
pub trait OnChargeTransaction<T: Trait> {
|
||||
/// The underlying integer type in which fees are calculated.
|
||||
type Balance: AtLeast32BitUnsigned + FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default;
|
||||
type LiquidityInfo: Default;
|
||||
|
||||
/// Before the transaction is executed the payment of the transaction fees
|
||||
/// need to be secured.
|
||||
///
|
||||
/// Note: The `fee` already includes the `tip`.
|
||||
fn withdraw_fee(
|
||||
who: &T::AccountId,
|
||||
call: &T::Call,
|
||||
dispatch_info: &DispatchInfoOf<T::Call>,
|
||||
fee: Self::Balance,
|
||||
tip: Self::Balance,
|
||||
) -> Result<Self::LiquidityInfo, TransactionValidityError>;
|
||||
|
||||
/// After the transaction was executed the actual fee can be calculated.
|
||||
/// This function should refund any overpaid fees and optionally deposit
|
||||
/// the corrected amount.
|
||||
///
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
/// Implements the transaction payment for a module implementing the `Currency`
|
||||
/// trait (eg. the pallet_balances) using an unbalance handler (implementing
|
||||
/// `OnUnbalanced`).
|
||||
pub struct CurrencyAdapter<C, OU>(PhantomData<(C, OU)>);
|
||||
|
||||
/// Default implementation for a Currency and an OnUnbalanced handler.
|
||||
impl<T, C, OU> OnChargeTransaction<T> for CurrencyAdapter<C, OU>
|
||||
where
|
||||
T: Trait,
|
||||
T::TransactionByteFee: Get<<C as Currency<<T as frame_system::Trait>::AccountId>>::Balance>,
|
||||
C: Currency<<T as frame_system::Trait>::AccountId>,
|
||||
C::PositiveImbalance:
|
||||
Imbalance<<C as Currency<<T as frame_system::Trait>::AccountId>>::Balance, Opposite = C::NegativeImbalance>,
|
||||
C::NegativeImbalance:
|
||||
Imbalance<<C as Currency<<T as frame_system::Trait>::AccountId>>::Balance, Opposite = C::PositiveImbalance>,
|
||||
OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,
|
||||
{
|
||||
type LiquidityInfo = Option<NegativeImbalanceOf<C, T>>;
|
||||
type Balance = <C as Currency<<T as frame_system::Trait>::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);
|
||||
}
|
||||
|
||||
let withdraw_reason = if tip.is_zero() {
|
||||
WithdrawReasons::TRANSACTION_PAYMENT
|
||||
} else {
|
||||
WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
|
||||
};
|
||||
|
||||
match C::withdraw(who, fee, withdraw_reason, ExistenceRequirement::KeepAlive) {
|
||||
Ok(imbalance) => Ok(Some(imbalance)),
|
||||
Err(_) => Err(InvalidTransaction::Payment.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::Call>,
|
||||
_post_info: &PostDispatchInfoOf<T::Call>,
|
||||
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 refund_amount = paid.peek().saturating_sub(corrected_fee);
|
||||
// refund to the the account that paid the fees. If this fails, the
|
||||
// account might have dropped below the existential balance. In
|
||||
// that case we don't refund anything.
|
||||
let refund_imbalance =
|
||||
C::deposit_into_existing(&who, refund_amount).unwrap_or_else(|_| C::PositiveImbalance::zero());
|
||||
// merge the imbalance caused by paying the fees and refunding parts of it again.
|
||||
let adjusted_paid = paid
|
||||
.offset(refund_imbalance)
|
||||
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
|
||||
// Call someone else to handle the imbalance (fee and tip separately)
|
||||
let imbalances = adjusted_paid.split(tip);
|
||||
OU::on_unbalanceds(Some(imbalances.0).into_iter().chain(Some(imbalances.1)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user