#![cfg_attr(not(feature = "std"), no_std)]
use sp_std::prelude::*;
use codec::{Encode, Decode};
use frame_support::{
decl_storage, decl_module,
traits::Get,
weights::{
Weight, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Pays, WeightToFeePolynomial,
WeightToFeeCoefficient, DispatchClass,
},
dispatch::DispatchResult,
};
use sp_runtime::{
FixedU128, FixedPointNumber, FixedPointOperand, Perquintill, RuntimeDebug,
transaction_validity::{
TransactionPriority, ValidTransaction, TransactionValidityError, TransactionValidity,
},
traits::{
Saturating, SignedExtension, SaturatedConversion, Convert, Dispatchable,
DispatchInfoOf, PostDispatchInfoOf,
},
};
mod payment;
mod types;
pub use payment::*;
pub use types::{InclusionFee, FeeDetails, RuntimeDispatchInfo};
pub type Multiplier = FixedU128;
type BalanceOf<T> =
<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;
pub struct TargetedFeeAdjustment<T, S, V, M>(sp_std::marker::PhantomData<(T, S, V, M)>);
pub trait MultiplierUpdate: Convert<Multiplier, Multiplier> {
fn min() -> Multiplier;
fn target() -> Perquintill;
fn variability() -> Multiplier;
}
impl MultiplierUpdate for () {
fn min() -> Multiplier {
Default::default()
}
fn target() -> Perquintill {
Default::default()
}
fn variability() -> Multiplier {
Default::default()
}
}
impl<T, S, V, M> MultiplierUpdate for TargetedFeeAdjustment<T, S, V, M>
where T: frame_system::Config, S: Get<Perquintill>, V: Get<Multiplier>, M: Get<Multiplier>,
{
fn min() -> Multiplier {
M::get()
}
fn target() -> Perquintill {
S::get()
}
fn variability() -> Multiplier {
V::get()
}
}
impl<T, S, V, M> Convert<Multiplier, Multiplier> for TargetedFeeAdjustment<T, S, V, M>
where T: frame_system::Config, S: Get<Perquintill>, V: Get<Multiplier>, M: Get<Multiplier>,
{
fn convert(previous: Multiplier) -> Multiplier {
let min_multiplier = M::get();
let previous = previous.max(min_multiplier);
let weights = T::BlockWeights::get();
let normal_max_weight = weights.get(DispatchClass::Normal).max_total
.unwrap_or_else(|| weights.max_block);
let current_block_weight = <frame_system::Module<T>>::block_weight();
let normal_block_weight = *current_block_weight
.get(DispatchClass::Normal)
.min(&normal_max_weight);
let s = S::get();
let v = V::get();
let target_weight = (s * normal_max_weight) as u128;
let block_weight = normal_block_weight as u128;
let positive = block_weight >= target_weight;
let diff_abs = block_weight.max(target_weight) - block_weight.min(target_weight);
let diff = Multiplier::saturating_from_rational(diff_abs, normal_max_weight.max(1));
let diff_squared = diff.saturating_mul(diff);
let v_squared_2 = v.saturating_mul(v) / Multiplier::saturating_from_integer(2);
let first_term = v.saturating_mul(diff);
let second_term = v_squared_2.saturating_mul(diff_squared);
if positive {
let excess = first_term.saturating_add(second_term).saturating_mul(previous);
previous.saturating_add(excess).max(min_multiplier)
} else {
let negative = first_term.saturating_sub(second_term).saturating_mul(previous);
previous.saturating_sub(negative).max(min_multiplier)
}
}
}
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)]
enum Releases {
V1Ancient,
V2,
}
impl Default for Releases {
fn default() -> Self {
Releases::V1Ancient
}
}
pub trait Config: frame_system::Config {
type OnChargeTransaction: OnChargeTransaction<Self>;
type TransactionByteFee: Get<BalanceOf<Self>>;
type WeightToFee: WeightToFeePolynomial<Balance=BalanceOf<Self>>;
type FeeMultiplierUpdate: MultiplierUpdate;
}
decl_storage! {
trait Store for Module<T: Config> as TransactionPayment {
pub NextFeeMultiplier get(fn next_fee_multiplier): Multiplier = Multiplier::saturating_from_integer(1);
StorageVersion build(|_: &GenesisConfig| Releases::V2): Releases;
}
}
decl_module! {
pub struct Module<T: Config> for enum Call where origin: T::Origin {
const TransactionByteFee: BalanceOf<T> = T::TransactionByteFee::get();
const WeightToFee: Vec<WeightToFeeCoefficient<BalanceOf<T>>> =
T::WeightToFee::polynomial().to_vec();
fn on_finalize() {
NextFeeMultiplier::mutate(|fm| {
*fm = T::FeeMultiplierUpdate::convert(*fm);
});
}
fn integrity_test() {
use sp_std::convert::TryInto;
assert!(
<Multiplier as sp_runtime::traits::Bounded>::max_value() >=
Multiplier::checked_from_integer(
T::BlockWeights::get().max_block.try_into().unwrap()
).unwrap(),
);
let min_value = T::FeeMultiplierUpdate::min();
let mut target = T::FeeMultiplierUpdate::target() *
T::BlockWeights::get().get(DispatchClass::Normal).max_total.expect(
"Setting `max_total` for `Normal` dispatch class is not compatible with \
`transaction-payment` pallet."
);
let addition = target / 100;
if addition == 0 {
return;
}
target += addition;
sp_io::TestExternalities::new_empty().execute_with(|| {
<frame_system::Module<T>>::set_block_consumed_resources(target, 0);
let next = T::FeeMultiplierUpdate::convert(min_value);
assert!(next > min_value, "The minimum bound of the multiplier is too low. When \
block saturation is more than target by 1% and multiplier is minimal then \
the multiplier doesn't increase."
);
})
}
}
}
impl<T: Config> Module<T> where
BalanceOf<T>: FixedPointOperand
{
pub fn query_info<Extrinsic: GetDispatchInfo>(
unchecked_extrinsic: Extrinsic,
len: u32,
) -> RuntimeDispatchInfo<BalanceOf<T>>
where
T::Call: Dispatchable<Info=DispatchInfo>,
{
let dispatch_info = <Extrinsic as GetDispatchInfo>::get_dispatch_info(&unchecked_extrinsic);
let partial_fee = Self::compute_fee(len, &dispatch_info, 0u32.into());
let DispatchInfo { weight, class, .. } = dispatch_info;
RuntimeDispatchInfo { weight, class, partial_fee }
}
pub fn query_fee_details<Extrinsic: GetDispatchInfo>(
unchecked_extrinsic: Extrinsic,
len: u32,
) -> FeeDetails<BalanceOf<T>>
where
T::Call: Dispatchable<Info=DispatchInfo>,
{
let dispatch_info = <Extrinsic as GetDispatchInfo>::get_dispatch_info(&unchecked_extrinsic);
Self::compute_fee_details(len, &dispatch_info, 0u32.into())
}
pub fn compute_fee(
len: u32,
info: &DispatchInfoOf<T::Call>,
tip: BalanceOf<T>,
) -> BalanceOf<T> where
T::Call: Dispatchable<Info=DispatchInfo>,
{
Self::compute_fee_details(len, info, tip).final_fee()
}
pub fn compute_fee_details(
len: u32,
info: &DispatchInfoOf<T::Call>,
tip: BalanceOf<T>,
) -> FeeDetails<BalanceOf<T>> where
T::Call: Dispatchable<Info=DispatchInfo>,
{
Self::compute_fee_raw(len, info.weight, tip, info.pays_fee, info.class)
}
pub fn compute_actual_fee(
len: u32,
info: &DispatchInfoOf<T::Call>,
post_info: &PostDispatchInfoOf<T::Call>,
tip: BalanceOf<T>,
) -> BalanceOf<T> where
T::Call: Dispatchable<Info=DispatchInfo,PostInfo=PostDispatchInfo>,
{
Self::compute_actual_fee_details(len, info, post_info, tip).final_fee()
}
pub fn compute_actual_fee_details(
len: u32,
info: &DispatchInfoOf<T::Call>,
post_info: &PostDispatchInfoOf<T::Call>,
tip: BalanceOf<T>,
) -> FeeDetails<BalanceOf<T>> where
T::Call: Dispatchable<Info=DispatchInfo,PostInfo=PostDispatchInfo>,
{
Self::compute_fee_raw(
len,
post_info.calc_actual_weight(info),
tip,
post_info.pays_fee(info),
info.class,
)
}
fn compute_fee_raw(
len: u32,
weight: Weight,
tip: BalanceOf<T>,
pays_fee: Pays,
class: DispatchClass,
) -> FeeDetails<BalanceOf<T>> {
if pays_fee == Pays::Yes {
let len = <BalanceOf<T>>::from(len);
let per_byte = T::TransactionByteFee::get();
let fixed_len_fee = per_byte.saturating_mul(len);
let unadjusted_weight_fee = Self::weight_to_fee(weight);
let multiplier = Self::next_fee_multiplier();
let adjusted_weight_fee = multiplier.saturating_mul_int(unadjusted_weight_fee);
let base_fee = Self::weight_to_fee(T::BlockWeights::get().get(class).base_extrinsic);
FeeDetails {
inclusion_fee: Some(InclusionFee {
base_fee,
len_fee: fixed_len_fee,
adjusted_weight_fee
}),
tip
}
} else {
FeeDetails {
inclusion_fee: None,
tip
}
}
}
fn weight_to_fee(weight: Weight) -> BalanceOf<T> {
let capped_weight = weight.min(T::BlockWeights::get().max_block);
T::WeightToFee::calc(&capped_weight)
}
}
impl<T> Convert<Weight, BalanceOf<T>> for Module<T> where
T: Config,
BalanceOf<T>: FixedPointOperand,
{
fn convert(weight: Weight) -> BalanceOf<T> {
NextFeeMultiplier::get().saturating_mul_int(Self::weight_to_fee(weight))
}
}
#[derive(Encode, Decode, Clone, Eq, PartialEq)]
pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
impl<T: Config> ChargeTransactionPayment<T> where
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
BalanceOf<T>: Send + Sync + FixedPointOperand,
{
pub fn from(fee: BalanceOf<T>) -> Self {
Self(fee)
}
fn withdraw_fee(
&self,
who: &T::AccountId,
call: &T::Call,
info: &DispatchInfoOf<T::Call>,
len: usize,
) -> Result<
(
BalanceOf<T>,
<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo,
),
TransactionValidityError,
> {
let tip = self.0;
let fee = Module::<T>::compute_fee(len as u32, info, tip);
<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::withdraw_fee(who, call, info, fee, tip)
.map(|i| (fee, i))
}
fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {
let weight_saturation = T::BlockWeights::get().max_block / info.weight.max(1);
let max_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);
let len_saturation = max_block_length as u64 / (len as u64).max(1);
let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();
final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()
}
}
impl<T: Config> sp_std::fmt::Debug for ChargeTransactionPayment<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
write!(f, "ChargeTransactionPayment<{:?}>", self.0)
}
#[cfg(not(feature = "std"))]
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Ok(())
}
}
impl<T: Config> SignedExtension for ChargeTransactionPayment<T> where
BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,
T::Call: Dispatchable<Info=DispatchInfo, PostInfo=PostDispatchInfo>,
{
const IDENTIFIER: &'static str = "ChargeTransactionPayment";
type AccountId = T::AccountId;
type Call = T::Call;
type AdditionalSigned = ();
type Pre = (
BalanceOf<T>,
Self::AccountId,
<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo,
);
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) }
fn validate(
&self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize,
) -> TransactionValidity {
let (fee, _) = self.withdraw_fee(who, call, info, len)?;
Ok(ValidTransaction {
priority: Self::get_priority(len, info, fee),
..Default::default()
})
}
fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &DispatchInfoOf<Self::Call>,
len: usize
) -> Result<Self::Pre, TransactionValidityError> {
let (_fee, imbalance) = self.withdraw_fee(who, call, info, len)?;
Ok((self.0, who.clone(), imbalance))
}
fn post_dispatch(
pre: Self::Pre,
info: &DispatchInfoOf<Self::Call>,
post_info: &PostDispatchInfoOf<Self::Call>,
len: usize,
_result: &DispatchResult,
) -> Result<(), TransactionValidityError> {
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(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate as pallet_transaction_payment;
use frame_system as system;
use codec::Encode;
use frame_support::{
parameter_types,
weights::{
DispatchClass, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Weight,
WeightToFeePolynomial, WeightToFeeCoefficients, WeightToFeeCoefficient,
},
traits::Currency,
};
use pallet_balances::Call as BalancesCall;
use sp_core::H256;
use sp_runtime::{
testing::{Header, TestXt},
traits::{BlakeTwo256, IdentityLookup},
Perbill,
};
use std::cell::RefCell;
use smallvec::smallvec;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
type Block = frame_system::mocking::MockBlock<Runtime>;
frame_support::construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: system::{Module, Call, Config, Storage, Event<T>},
Balances: pallet_balances::{Module, Call, Storage, Config<T>, Event<T>},
TransactionPayment: pallet_transaction_payment::{Module, Storage},
}
);
const CALL: &<Runtime as frame_system::Config>::Call =
&Call::Balances(BalancesCall::transfer(2, 69));
thread_local! {
static EXTRINSIC_BASE_WEIGHT: RefCell<u64> = RefCell::new(0);
}
pub struct BlockWeights;
impl Get<frame_system::limits::BlockWeights> for BlockWeights {
fn get() -> frame_system::limits::BlockWeights {
frame_system::limits::BlockWeights::builder()
.base_block(0)
.for_class(DispatchClass::all(), |weights| {
weights.base_extrinsic = EXTRINSIC_BASE_WEIGHT.with(|v| *v.borrow()).into();
})
.for_class(DispatchClass::non_mandatory(), |weights| {
weights.max_total = 1024.into();
})
.build_or_panic()
}
}
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub static TransactionByteFee: u64 = 1;
pub static WeightToFee: u64 = 1;
}
impl frame_system::Config for Runtime {
type BaseCallFilter = ();
type BlockWeights = BlockWeights;
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = Call;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
impl pallet_balances::Config for Runtime {
type Balance = u64;
type Event = Event;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type MaxLocks = ();
type WeightInfo = ();
}
impl WeightToFeePolynomial for WeightToFee {
type Balance = u64;
fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
smallvec![WeightToFeeCoefficient {
degree: 1,
coeff_frac: Perbill::zero(),
coeff_integer: WEIGHT_TO_FEE.with(|v| *v.borrow()),
negative: false,
}]
}
}
impl Config for Runtime {
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = WeightToFee;
type FeeMultiplierUpdate = ();
}
pub struct ExtBuilder {
balance_factor: u64,
base_weight: u64,
byte_fee: u64,
weight_to_fee: u64
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
balance_factor: 1,
base_weight: 0,
byte_fee: 1,
weight_to_fee: 1,
}
}
}
impl ExtBuilder {
pub fn base_weight(mut self, base_weight: u64) -> Self {
self.base_weight = base_weight;
self
}
pub fn byte_fee(mut self, byte_fee: u64) -> Self {
self.byte_fee = byte_fee;
self
}
pub fn weight_fee(mut self, weight_to_fee: u64) -> Self {
self.weight_to_fee = weight_to_fee;
self
}
pub fn balance_factor(mut self, factor: u64) -> Self {
self.balance_factor = factor;
self
}
fn set_constants(&self) {
EXTRINSIC_BASE_WEIGHT.with(|v| *v.borrow_mut() = self.base_weight);
TRANSACTION_BYTE_FEE.with(|v| *v.borrow_mut() = self.byte_fee);
WEIGHT_TO_FEE.with(|v| *v.borrow_mut() = self.weight_to_fee);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_constants();
let mut t = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
pallet_balances::GenesisConfig::<Runtime> {
balances: if self.balance_factor > 0 {
vec![
(1, 10 * self.balance_factor),
(2, 20 * self.balance_factor),
(3, 30 * self.balance_factor),
(4, 40 * self.balance_factor),
(5, 50 * self.balance_factor),
(6, 60 * self.balance_factor)
]
} else {
vec![]
},
}.assimilate_storage(&mut t).unwrap();
t.into()
}
}
pub fn info_from_weight(w: Weight) -> DispatchInfo {
DispatchInfo { weight: w, ..Default::default() }
}
fn post_info_from_weight(w: Weight) -> PostDispatchInfo {
PostDispatchInfo {
actual_weight: Some(w),
pays_fee: Default::default(),
}
}
fn post_info_from_pays(p: Pays) -> PostDispatchInfo {
PostDispatchInfo {
actual_weight: None,
pays_fee: p,
}
}
fn default_post_info() -> PostDispatchInfo {
PostDispatchInfo {
actual_weight: None,
pays_fee: Default::default(),
}
}
#[test]
fn signed_extension_transaction_payment_work() {
ExtBuilder::default()
.balance_factor(10)
.base_weight(5)
.build()
.execute_with(||
{
let len = 10;
let pre = ChargeTransactionPayment::<Runtime>::from(0)
.pre_dispatch(&1, CALL, &info_from_weight(5), len)
.unwrap();
assert_eq!(Balances::free_balance(1), 100 - 5 - 5 - 10);
assert!(
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &info_from_weight(5), &default_post_info(), len, &Ok(()))
.is_ok()
);
assert_eq!(Balances::free_balance(1), 100 - 5 - 5 - 10);
let pre = ChargeTransactionPayment::<Runtime>::from(5 )
.pre_dispatch(&2, CALL, &info_from_weight(100), len)
.unwrap();
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5);
assert!(
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &info_from_weight(100), &post_info_from_weight(50), len, &Ok(()))
.is_ok()
);
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 50 - 5);
});
}
#[test]
fn signed_extension_transaction_payment_multiplied_refund_works() {
ExtBuilder::default()
.balance_factor(10)
.base_weight(5)
.build()
.execute_with(||
{
let len = 10;
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
let pre = ChargeTransactionPayment::<Runtime>::from(5 )
.pre_dispatch(&2, CALL, &info_from_weight(100), len)
.unwrap();
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 150 - 5);
assert!(
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &info_from_weight(100), &post_info_from_weight(50), len, &Ok(()))
.is_ok()
);
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 75 - 5);
});
}
#[test]
fn signed_extension_transaction_payment_is_bounded() {
ExtBuilder::default()
.balance_factor(1000)
.byte_fee(0)
.build()
.execute_with(||
{
assert!(
ChargeTransactionPayment::<Runtime>::from(0)
.pre_dispatch(&1, CALL, &info_from_weight(Weight::max_value()), 10)
.is_ok()
);
assert_eq!(
Balances::free_balance(&1),
(10000 - <Runtime as frame_system::Config>::BlockWeights::get().max_block) as u64
);
});
}
#[test]
fn signed_extension_allows_free_transactions() {
ExtBuilder::default()
.base_weight(100)
.balance_factor(0)
.build()
.execute_with(||
{
assert_eq!(Balances::free_balance(1), 0);
let len = 100;
let operational_transaction = DispatchInfo {
weight: 0,
class: DispatchClass::Operational,
pays_fee: Pays::No,
};
assert!(
ChargeTransactionPayment::<Runtime>::from(0)
.validate(&1, CALL, &operational_transaction , len)
.is_ok()
);
let free_transaction = DispatchInfo {
weight: 0,
class: DispatchClass::Normal,
pays_fee: Pays::Yes,
};
assert!(
ChargeTransactionPayment::<Runtime>::from(0)
.validate(&1, CALL, &free_transaction , len)
.is_err()
);
});
}
#[test]
fn signed_ext_length_fee_is_also_updated_per_congestion() {
ExtBuilder::default()
.base_weight(5)
.balance_factor(10)
.build()
.execute_with(||
{
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
let len = 10;
assert!(
ChargeTransactionPayment::<Runtime>::from(10)
.pre_dispatch(&1, CALL, &info_from_weight(3), len)
.is_ok()
);
assert_eq!(
Balances::free_balance(1),
100
- 10
- 5
- 10
- (3 * 3 / 2)
);
})
}
#[test]
fn query_info_works() {
let call = Call::Balances(BalancesCall::transfer(2, 69));
let origin = 111111;
let extra = ();
let xt = TestXt::new(call, Some((origin, extra)));
let info = xt.get_dispatch_info();
let ext = xt.encode();
let len = ext.len() as u32;
ExtBuilder::default()
.base_weight(5)
.weight_fee(2)
.build()
.execute_with(||
{
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
assert_eq!(
TransactionPayment::query_info(xt, len),
RuntimeDispatchInfo {
weight: info.weight,
class: info.class,
partial_fee:
5 * 2
+ len as u64
+ info.weight.min(BlockWeights::get().max_block) as u64 * 2 * 3 / 2
},
);
});
}
#[test]
fn compute_fee_works_without_multiplier() {
ExtBuilder::default()
.base_weight(100)
.byte_fee(10)
.balance_factor(0)
.build()
.execute_with(||
{
assert_eq!(NextFeeMultiplier::get(), Multiplier::one());
let dispatch_info = DispatchInfo {
weight: 0,
class: DispatchClass::Operational,
pays_fee: Pays::No,
};
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 10), 10);
let dispatch_info = DispatchInfo {
weight: 0,
class: DispatchClass::Operational,
pays_fee: Pays::Yes,
};
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 0), 100);
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 69), 169);
assert_eq!(Module::<Runtime>::compute_fee(42, &dispatch_info, 0), 520);
let dispatch_info = DispatchInfo {
weight: 1000,
class: DispatchClass::Operational,
pays_fee: Pays::Yes,
};
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 0), 1100);
});
}
#[test]
fn compute_fee_works_with_multiplier() {
ExtBuilder::default()
.base_weight(100)
.byte_fee(10)
.balance_factor(0)
.build()
.execute_with(||
{
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
let dispatch_info = DispatchInfo {
weight: 0,
class: DispatchClass::Operational,
pays_fee: Pays::Yes,
};
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 0), 100);
let dispatch_info = DispatchInfo {
weight: 123,
class: DispatchClass::Operational,
pays_fee: Pays::Yes,
};
assert_eq!(
Module::<Runtime>::compute_fee(456, &dispatch_info, 789),
100 + (3 * 123 / 2) + 4560 + 789,
);
});
}
#[test]
fn compute_fee_works_with_negative_multiplier() {
ExtBuilder::default()
.base_weight(100)
.byte_fee(10)
.balance_factor(0)
.build()
.execute_with(||
{
NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2));
let dispatch_info = DispatchInfo {
weight: 0,
class: DispatchClass::Operational,
pays_fee: Pays::Yes,
};
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 0), 100);
let dispatch_info = DispatchInfo {
weight: 123,
class: DispatchClass::Operational,
pays_fee: Pays::Yes,
};
assert_eq!(
Module::<Runtime>::compute_fee(456, &dispatch_info, 789),
100 + (123 / 2) + 4560 + 789,
);
});
}
#[test]
fn compute_fee_does_not_overflow() {
ExtBuilder::default()
.base_weight(100)
.byte_fee(10)
.balance_factor(0)
.build()
.execute_with(||
{
let dispatch_info = DispatchInfo {
weight: Weight::max_value(),
class: DispatchClass::Operational,
pays_fee: Pays::Yes,
};
assert_eq!(
Module::<Runtime>::compute_fee(
<u32>::max_value(),
&dispatch_info,
<u64>::max_value()
),
<u64>::max_value()
);
});
}
#[test]
fn refund_does_not_recreate_account() {
ExtBuilder::default()
.balance_factor(10)
.base_weight(5)
.build()
.execute_with(||
{
System::set_block_number(10);
let len = 10;
let pre = ChargeTransactionPayment::<Runtime>::from(5 )
.pre_dispatch(&2, CALL, &info_from_weight(100), len)
.unwrap();
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5);
assert!(Balances::transfer(Some(2).into(), 3, Balances::free_balance(2)).is_ok());
assert_eq!(Balances::free_balance(2), 0);
assert!(
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &info_from_weight(100), &post_info_from_weight(50), len, &Ok(()))
.is_ok()
);
assert_eq!(Balances::free_balance(2), 0);
assert!(System::events().iter().any(|event| {
event.event == Event::pallet_balances(pallet_balances::Event::Transfer(2, 3, 80))
}));
assert!(System::events().iter().any(|event| {
event.event == Event::system(system::Event::KilledAccount(2))
}));
});
}
#[test]
fn actual_weight_higher_than_max_refunds_nothing() {
ExtBuilder::default()
.balance_factor(10)
.base_weight(5)
.build()
.execute_with(||
{
let len = 10;
let pre = ChargeTransactionPayment::<Runtime>::from(5 )
.pre_dispatch(&2, CALL, &info_from_weight(100), len)
.unwrap();
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5);
assert!(
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &info_from_weight(100), &post_info_from_weight(101), len, &Ok(()))
.is_ok()
);
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5);
});
}
#[test]
fn zero_transfer_on_free_transaction() {
ExtBuilder::default()
.balance_factor(10)
.base_weight(5)
.build()
.execute_with(||
{
System::set_block_number(10);
let len = 10;
let dispatch_info = DispatchInfo {
weight: 100,
pays_fee: Pays::No,
class: DispatchClass::Normal,
};
let user = 69;
let pre = ChargeTransactionPayment::<Runtime>::from(0)
.pre_dispatch(&user, CALL, &dispatch_info, len)
.unwrap();
assert_eq!(Balances::total_balance(&user), 0);
assert!(
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &dispatch_info, &default_post_info(), len, &Ok(()))
.is_ok()
);
assert_eq!(Balances::total_balance(&user), 0);
assert_eq!(System::events().len(), 0);
});
}
#[test]
fn refund_consistent_with_actual_weight() {
ExtBuilder::default()
.balance_factor(10)
.base_weight(7)
.build()
.execute_with(||
{
let info = info_from_weight(100);
let post_info = post_info_from_weight(33);
let prev_balance = Balances::free_balance(2);
let len = 10;
let tip = 5;
NextFeeMultiplier::put(Multiplier::saturating_from_rational(5, 4));
let pre = ChargeTransactionPayment::<Runtime>::from(tip)
.pre_dispatch(&2, CALL, &info, len)
.unwrap();
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &info, &post_info, len, &Ok(()))
.unwrap();
let refund_based_fee = prev_balance - Balances::free_balance(2);
let actual_fee = Module::<Runtime>
::compute_actual_fee(len as u32, &info, &post_info, tip);
assert_eq!(actual_fee, 7 + 10 + (33 * 5 / 4) + 5);
assert_eq!(refund_based_fee, actual_fee);
});
}
#[test]
fn post_info_can_change_pays_fee() {
ExtBuilder::default()
.balance_factor(10)
.base_weight(7)
.build()
.execute_with(||
{
let info = info_from_weight(100);
let post_info = post_info_from_pays(Pays::No);
let prev_balance = Balances::free_balance(2);
let len = 10;
let tip = 5;
NextFeeMultiplier::put(Multiplier::saturating_from_rational(5, 4));
let pre = ChargeTransactionPayment::<Runtime>::from(tip)
.pre_dispatch(&2, CALL, &info, len)
.unwrap();
ChargeTransactionPayment::<Runtime>
::post_dispatch(pre, &info, &post_info, len, &Ok(()))
.unwrap();
let refund_based_fee = prev_balance - Balances::free_balance(2);
let actual_fee = Module::<Runtime>
::compute_actual_fee(len as u32, &info, &post_info, tip);
assert_eq!(actual_fee, 5);
assert_eq!(refund_based_fee, actual_fee);
});
}
}