mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 03:31:10 +00:00
Fix the broken weight multiplier update function (#6334)
* Initial draft, has some todos left * remove ununsed import * Apply suggestions from code review * Some refactors with migration * Fix more test and cleanup * Fix for companion * Apply suggestions from code review Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Update bin/node/runtime/src/impls.rs * Fix weight * Add integrity test * length is not affected. Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
This commit is contained in:
Generated
+1
@@ -4587,6 +4587,7 @@ dependencies = [
|
||||
"pallet-balances",
|
||||
"pallet-transaction-payment-rpc-runtime-api",
|
||||
"parity-scale-codec",
|
||||
"serde",
|
||||
"smallvec 1.4.0",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
|
||||
@@ -19,14 +19,11 @@ use codec::{Encode, Decode, Joiner};
|
||||
use frame_support::{
|
||||
StorageValue, StorageMap,
|
||||
traits::Currency,
|
||||
weights::{
|
||||
GetDispatchInfo, DispatchInfo, DispatchClass, constants::ExtrinsicBaseWeight,
|
||||
WeightToFeePolynomial,
|
||||
},
|
||||
weights::{GetDispatchInfo, DispatchInfo, DispatchClass},
|
||||
};
|
||||
use sp_core::{NeverNativeValue, traits::Externalities, storage::well_known_keys};
|
||||
use sp_runtime::{
|
||||
ApplyExtrinsicResult, FixedI128, FixedPointNumber,
|
||||
ApplyExtrinsicResult,
|
||||
traits::Hash as HashT,
|
||||
transaction_validity::InvalidTransaction,
|
||||
};
|
||||
@@ -35,7 +32,7 @@ use frame_system::{self, EventRecord, Phase};
|
||||
|
||||
use node_runtime::{
|
||||
Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances,
|
||||
System, TransactionPayment, Event, TransactionByteFee,
|
||||
System, TransactionPayment, Event,
|
||||
constants::currency::*,
|
||||
};
|
||||
use node_primitives::{Balance, Hash};
|
||||
@@ -52,16 +49,17 @@ use self::common::{*, sign};
|
||||
/// test code paths that differ between native and wasm versions.
|
||||
pub const BLOATY_CODE: &[u8] = node_runtime::WASM_BINARY_BLOATY;
|
||||
|
||||
/// Default transfer fee
|
||||
fn transfer_fee<E: Encode>(extrinsic: &E, fee_multiplier: FixedI128) -> Balance {
|
||||
let length_fee = TransactionByteFee::get() * (extrinsic.encode().len() as Balance);
|
||||
|
||||
let base_weight = ExtrinsicBaseWeight::get();
|
||||
let base_fee = <Runtime as pallet_transaction_payment::Trait>::WeightToFee::calc(&base_weight);
|
||||
let weight = default_transfer_call().get_dispatch_info().weight;
|
||||
let weight_fee = <Runtime as pallet_transaction_payment::Trait>::WeightToFee::calc(&weight);
|
||||
|
||||
base_fee + fee_multiplier.saturating_mul_acc_int(length_fee + weight_fee)
|
||||
/// Default transfer fee. This will use the same logic that is implemented in transaction-payment module.
|
||||
///
|
||||
/// Note that reads the multiplier from storage directly, hence to get the fee of `extrinsic`
|
||||
/// at block `n`, it must be called prior to executing block `n` to do the calculation with the
|
||||
/// correct multiplier.
|
||||
fn transfer_fee<E: Encode>(extrinsic: &E) -> Balance {
|
||||
TransactionPayment::compute_fee(
|
||||
extrinsic.encode().len() as u32,
|
||||
&default_transfer_call().get_dispatch_info(),
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
fn xt() -> UncheckedExtrinsic {
|
||||
@@ -242,7 +240,7 @@ fn successful_execution_with_native_equivalent_code_gives_ok() {
|
||||
).0;
|
||||
assert!(r.is_ok());
|
||||
|
||||
let fm = t.execute_with(TransactionPayment::next_fee_multiplier);
|
||||
let fees = t.execute_with(|| transfer_fee(&xt()));
|
||||
|
||||
let r = executor_call::<NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
@@ -254,7 +252,6 @@ fn successful_execution_with_native_equivalent_code_gives_ok() {
|
||||
assert!(r.is_ok());
|
||||
|
||||
t.execute_with(|| {
|
||||
let fees = transfer_fee(&xt(), fm);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees);
|
||||
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
|
||||
});
|
||||
@@ -286,7 +283,7 @@ fn successful_execution_with_foreign_code_gives_ok() {
|
||||
).0;
|
||||
assert!(r.is_ok());
|
||||
|
||||
let fm = t.execute_with(TransactionPayment::next_fee_multiplier);
|
||||
let fees = t.execute_with(|| transfer_fee(&xt()));
|
||||
|
||||
let r = executor_call::<NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
@@ -298,7 +295,6 @@ fn successful_execution_with_foreign_code_gives_ok() {
|
||||
assert!(r.is_ok());
|
||||
|
||||
t.execute_with(|| {
|
||||
let fees = transfer_fee(&xt(), fm);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees);
|
||||
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
|
||||
});
|
||||
@@ -311,7 +307,7 @@ fn full_native_block_import_works() {
|
||||
let (block1, block2) = blocks();
|
||||
|
||||
let mut alice_last_known_balance: Balance = Default::default();
|
||||
let mut fm = t.execute_with(TransactionPayment::next_fee_multiplier);
|
||||
let mut fees = t.execute_with(|| transfer_fee(&xt()));
|
||||
|
||||
executor_call::<NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
@@ -322,7 +318,6 @@ fn full_native_block_import_works() {
|
||||
).0.unwrap();
|
||||
|
||||
t.execute_with(|| {
|
||||
let fees = transfer_fee(&xt(), fm);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees);
|
||||
assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS);
|
||||
alice_last_known_balance = Balances::total_balance(&alice());
|
||||
@@ -361,7 +356,7 @@ fn full_native_block_import_works() {
|
||||
assert_eq!(System::events(), events);
|
||||
});
|
||||
|
||||
fm = t.execute_with(TransactionPayment::next_fee_multiplier);
|
||||
fees = t.execute_with(|| transfer_fee(&xt()));
|
||||
|
||||
executor_call::<NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
@@ -372,7 +367,6 @@ fn full_native_block_import_works() {
|
||||
).0.unwrap();
|
||||
|
||||
t.execute_with(|| {
|
||||
let fees = transfer_fee(&xt(), fm);
|
||||
assert_eq!(
|
||||
Balances::total_balance(&alice()),
|
||||
alice_last_known_balance - 10 * DOLLARS - fees,
|
||||
@@ -450,7 +444,7 @@ fn full_wasm_block_import_works() {
|
||||
let (block1, block2) = blocks();
|
||||
|
||||
let mut alice_last_known_balance: Balance = Default::default();
|
||||
let mut fm = t.execute_with(TransactionPayment::next_fee_multiplier);
|
||||
let mut fees = t.execute_with(|| transfer_fee(&xt()));
|
||||
|
||||
executor_call::<NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
@@ -461,12 +455,12 @@ fn full_wasm_block_import_works() {
|
||||
).0.unwrap();
|
||||
|
||||
t.execute_with(|| {
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - transfer_fee(&xt(), fm));
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees);
|
||||
assert_eq!(Balances::total_balance(&bob()), 169 * DOLLARS);
|
||||
alice_last_known_balance = Balances::total_balance(&alice());
|
||||
});
|
||||
|
||||
fm = t.execute_with(TransactionPayment::next_fee_multiplier);
|
||||
fees = t.execute_with(|| transfer_fee(&xt()));
|
||||
|
||||
executor_call::<NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
@@ -479,11 +473,11 @@ fn full_wasm_block_import_works() {
|
||||
t.execute_with(|| {
|
||||
assert_eq!(
|
||||
Balances::total_balance(&alice()),
|
||||
alice_last_known_balance - 10 * DOLLARS - transfer_fee(&xt(), fm),
|
||||
alice_last_known_balance - 10 * DOLLARS - fees,
|
||||
);
|
||||
assert_eq!(
|
||||
Balances::total_balance(&bob()),
|
||||
179 * DOLLARS - 1 * transfer_fee(&xt(), fm),
|
||||
179 * DOLLARS - 1 * fees,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -755,7 +749,7 @@ fn successful_execution_gives_ok() {
|
||||
assert_eq!(Balances::total_balance(&alice()), 111 * DOLLARS);
|
||||
});
|
||||
|
||||
let fm = t.execute_with(TransactionPayment::next_fee_multiplier);
|
||||
let fees = t.execute_with(|| transfer_fee(&xt()));
|
||||
|
||||
let r = executor_call::<NeverNativeValue, fn() -> _>(
|
||||
&mut t,
|
||||
@@ -770,7 +764,6 @@ fn successful_execution_gives_ok() {
|
||||
.expect("Extrinsic failed");
|
||||
|
||||
t.execute_with(|| {
|
||||
let fees = transfer_fee(&xt(), fm);
|
||||
assert_eq!(Balances::total_balance(&alice()), 42 * DOLLARS - fees);
|
||||
assert_eq!(Balances::total_balance(&bob()), 69 * DOLLARS);
|
||||
});
|
||||
|
||||
@@ -22,9 +22,9 @@ use frame_support::{
|
||||
weights::{GetDispatchInfo, constants::ExtrinsicBaseWeight, IdentityFee, WeightToFeePolynomial},
|
||||
};
|
||||
use sp_core::NeverNativeValue;
|
||||
use sp_runtime::{FixedPointNumber, FixedI128, Perbill};
|
||||
use sp_runtime::{Perbill, FixedPointNumber};
|
||||
use node_runtime::{
|
||||
CheckedExtrinsic, Call, Runtime, Balances, TransactionPayment,
|
||||
CheckedExtrinsic, Call, Runtime, Balances, TransactionPayment, Multiplier,
|
||||
TransactionByteFee,
|
||||
constants::currency::*,
|
||||
};
|
||||
@@ -38,8 +38,8 @@ use self::common::{*, sign};
|
||||
fn fee_multiplier_increases_and_decreases_on_big_weight() {
|
||||
let mut t = new_test_ext(COMPACT_CODE, false);
|
||||
|
||||
// initial fee multiplier must be zero
|
||||
let mut prev_multiplier = FixedI128::from_inner(0);
|
||||
// initial fee multiplier must be one.
|
||||
let mut prev_multiplier = Multiplier::one();
|
||||
|
||||
t.execute_with(|| {
|
||||
assert_eq!(TransactionPayment::next_fee_multiplier(), prev_multiplier);
|
||||
@@ -59,7 +59,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() {
|
||||
},
|
||||
CheckedExtrinsic {
|
||||
signed: Some((charlie(), signed_extra(0, 0))),
|
||||
function: Call::System(frame_system::Call::fill_block(Perbill::from_percent(90))),
|
||||
function: Call::System(frame_system::Call::fill_block(Perbill::from_percent(60))),
|
||||
}
|
||||
]
|
||||
);
|
||||
@@ -122,7 +122,7 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transaction_fee_is_correct_ultimate() {
|
||||
fn transaction_fee_is_correct() {
|
||||
// This uses the exact values of substrate-node.
|
||||
//
|
||||
// weight of transfer call as of now: 1_000_000
|
||||
|
||||
@@ -18,11 +18,9 @@
|
||||
//! Some configurable implementations as associated type for the substrate runtime.
|
||||
|
||||
use node_primitives::Balance;
|
||||
use sp_runtime::traits::{Convert, Saturating};
|
||||
use sp_runtime::{FixedPointNumber, Perquintill};
|
||||
use frame_support::traits::{OnUnbalanced, Currency, Get};
|
||||
use pallet_transaction_payment::Multiplier;
|
||||
use crate::{Balances, System, Authorship, MaximumBlockWeight, NegativeImbalance};
|
||||
use sp_runtime::traits::Convert;
|
||||
use frame_support::traits::{OnUnbalanced, Currency};
|
||||
use crate::{Balances, Authorship, NegativeImbalance};
|
||||
|
||||
pub struct Author;
|
||||
impl OnUnbalanced<NegativeImbalance> for Author {
|
||||
@@ -47,89 +45,63 @@ impl Convert<u128, Balance> for CurrencyToVoteHandler {
|
||||
fn convert(x: u128) -> Balance { x * Self::factor() }
|
||||
}
|
||||
|
||||
/// Update the given multiplier based on the following formula
|
||||
///
|
||||
/// diff = (previous_block_weight - target_weight)/max_weight
|
||||
/// v = 0.00004
|
||||
/// next_weight = weight * (1 + (v * diff) + (v * diff)^2 / 2)
|
||||
///
|
||||
/// Where `target_weight` must be given as the `Get` implementation of the `T` generic type.
|
||||
/// https://research.web3.foundation/en/latest/polkadot/Token%20Economics/#relay-chain-transaction-fees
|
||||
pub struct TargetedFeeAdjustment<T>(sp_std::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Get<Perquintill>> Convert<Multiplier, Multiplier> for TargetedFeeAdjustment<T> {
|
||||
fn convert(multiplier: Multiplier) -> Multiplier {
|
||||
let max_weight = MaximumBlockWeight::get();
|
||||
let block_weight = System::block_weight().total().min(max_weight);
|
||||
let target_weight = (T::get() * max_weight) as u128;
|
||||
let block_weight = block_weight as u128;
|
||||
|
||||
// determines if the first_term is positive
|
||||
let positive = block_weight >= target_weight;
|
||||
let diff_abs = block_weight.max(target_weight) - block_weight.min(target_weight);
|
||||
// safe, diff_abs cannot exceed u64.
|
||||
let diff = Multiplier::saturating_from_rational(diff_abs, max_weight.max(1));
|
||||
let diff_squared = diff.saturating_mul(diff);
|
||||
|
||||
// 0.00004 = 4/100_000 = 40_000/10^9
|
||||
let v = Multiplier::saturating_from_rational(4, 100_000);
|
||||
// 0.00004^2 = 16/10^10 Taking the future /2 into account... 8/10^10
|
||||
let v_squared_2 = Multiplier::saturating_from_rational(8, 10_000_000_000u64);
|
||||
|
||||
let first_term = v.saturating_mul(diff);
|
||||
let second_term = v_squared_2.saturating_mul(diff_squared);
|
||||
|
||||
if positive {
|
||||
// Note: this is merely bounded by how big the multiplier and the inner value can go,
|
||||
// not by any economical reasoning.
|
||||
let excess = first_term.saturating_add(second_term);
|
||||
multiplier.saturating_add(excess)
|
||||
} else {
|
||||
// Defensive-only: first_term > second_term. Safe subtraction.
|
||||
let negative = first_term.saturating_sub(second_term);
|
||||
multiplier.saturating_sub(negative)
|
||||
// despite the fact that apply_to saturates weight (final fee cannot go below 0)
|
||||
// it is crucially important to stop here and don't further reduce the weight fee
|
||||
// multiplier. While at -1, it means that the network is so un-congested that all
|
||||
// transactions have no weight fee. We stop here and only increase if the network
|
||||
// became more busy.
|
||||
.max(Multiplier::saturating_from_integer(-1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod multiplier_tests {
|
||||
use super::*;
|
||||
use sp_runtime::assert_eq_error_rate;
|
||||
use crate::{MaximumBlockWeight, AvailableBlockRatio, Runtime};
|
||||
use crate::{constants::currency::*, TransactionPayment, TargetBlockFullness};
|
||||
use sp_runtime::{assert_eq_error_rate, FixedPointNumber};
|
||||
use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
|
||||
|
||||
use crate::{
|
||||
constants::{currency::*, time::*},
|
||||
TransactionPayment, MaximumBlockWeight, AvailableBlockRatio, Runtime, TargetBlockFullness,
|
||||
AdjustmentVariable, System, MinimumMultiplier,
|
||||
};
|
||||
use frame_support::weights::{Weight, WeightToFeePolynomial};
|
||||
|
||||
fn max() -> Weight {
|
||||
MaximumBlockWeight::get()
|
||||
AvailableBlockRatio::get() * MaximumBlockWeight::get()
|
||||
}
|
||||
|
||||
fn min_multiplier() -> Multiplier {
|
||||
MinimumMultiplier::get()
|
||||
}
|
||||
|
||||
fn target() -> Weight {
|
||||
TargetBlockFullness::get() * max()
|
||||
}
|
||||
|
||||
// poc reference implementation.
|
||||
fn fee_multiplier_update(block_weight: Weight, previous: Multiplier) -> Multiplier {
|
||||
// update based on runtime impl.
|
||||
fn runtime_multiplier_update(fm: Multiplier) -> Multiplier {
|
||||
TargetedFeeAdjustment::<
|
||||
Runtime,
|
||||
TargetBlockFullness,
|
||||
AdjustmentVariable,
|
||||
MinimumMultiplier,
|
||||
>::convert(fm)
|
||||
}
|
||||
|
||||
// update based on reference impl.
|
||||
fn truth_value_update(block_weight: Weight, previous: Multiplier) -> Multiplier {
|
||||
let accuracy = Multiplier::accuracy() as f64;
|
||||
let previous_float = previous.into_inner() as f64 / accuracy;
|
||||
// bump if it is zero.
|
||||
let previous_float = previous_float.max(min_multiplier().into_inner() as f64 / accuracy);
|
||||
|
||||
// maximum tx weight
|
||||
let m = max() as f64;
|
||||
// block weight always truncated to max weight
|
||||
let block_weight = (block_weight as f64).min(m);
|
||||
let v: f64 = 0.00004;
|
||||
let v: f64 = AdjustmentVariable::get().to_fraction();
|
||||
|
||||
// Ideal saturation in terms of weight
|
||||
let ss = target() as f64;
|
||||
// Current saturation in terms of weight
|
||||
let s = block_weight;
|
||||
|
||||
let fm = v * (s/m - ss/m) + v.powi(2) * (s/m - ss/m).powi(2) / 2.0;
|
||||
let addition_fm = Multiplier::from_inner((fm * Multiplier::accuracy() as f64).round() as i128);
|
||||
previous.saturating_add(addition_fm)
|
||||
let t1 = v * (s/m - ss/m);
|
||||
let t2 = v.powi(2) * (s/m - ss/m).powi(2) / 2.0;
|
||||
let next_float = previous_float * (1.0 + t1 + t2);
|
||||
Multiplier::from_fraction(next_float)
|
||||
}
|
||||
|
||||
fn run_with_system_weight<F>(w: Weight, assertions: F) where F: Fn() -> () {
|
||||
@@ -142,11 +114,12 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fee_multiplier_update_poc_works() {
|
||||
let fm = Multiplier::saturating_from_rational(0, 1);
|
||||
fn truth_value_update_poc_works() {
|
||||
let fm = Multiplier::saturating_from_rational(1, 2);
|
||||
let test_set = vec![
|
||||
(0, fm.clone()),
|
||||
(100, fm.clone()),
|
||||
(1000, fm.clone()),
|
||||
(target(), fm.clone()),
|
||||
(max() / 2, fm.clone()),
|
||||
(max(), fm.clone()),
|
||||
@@ -154,37 +127,71 @@ mod tests {
|
||||
test_set.into_iter().for_each(|(w, fm)| {
|
||||
run_with_system_weight(w, || {
|
||||
assert_eq_error_rate!(
|
||||
fee_multiplier_update(w, fm),
|
||||
TargetedFeeAdjustment::<TargetBlockFullness>::convert(fm),
|
||||
// Error is only 1 in 10^18
|
||||
Multiplier::from_inner(1),
|
||||
truth_value_update(w, fm),
|
||||
runtime_multiplier_update(fm),
|
||||
// Error is only 1 in 100^18
|
||||
Multiplier::from_inner(100),
|
||||
);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_chain_simulation() {
|
||||
// just a few txs per_block.
|
||||
let block_weight = 0;
|
||||
run_with_system_weight(block_weight, || {
|
||||
let mut fm = Multiplier::default();
|
||||
fn multiplier_can_grow_from_zero() {
|
||||
// if the min is too small, then this will not change, and we are doomed forever.
|
||||
// the weight is 1/100th bigger than target.
|
||||
run_with_system_weight(target() * 101 / 100, || {
|
||||
let next = runtime_multiplier_update(min_multiplier());
|
||||
assert!(next > min_multiplier(), "{:?} !>= {:?}", next, min_multiplier());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiplier_cannot_go_below_limit() {
|
||||
// will not go any further below even if block is empty.
|
||||
run_with_system_weight(0, || {
|
||||
let next = runtime_multiplier_update(min_multiplier());
|
||||
assert_eq!(next, min_multiplier());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_to_reach_zero() {
|
||||
// blocks per 24h in substrate-node: 28,800 (k)
|
||||
// s* = 0.1875
|
||||
// The bound from the research in an empty chain is:
|
||||
// v <~ (p / k(0 - s*))
|
||||
// p > v * k * -0.1875
|
||||
// to get p == -1 we'd need
|
||||
// -1 > 0.00001 * k * -0.1875
|
||||
// 1 < 0.00001 * k * 0.1875
|
||||
// 10^9 / 1875 < k
|
||||
// k > 533_333 ~ 18,5 days.
|
||||
run_with_system_weight(0, || {
|
||||
// start from 1, the default.
|
||||
let mut fm = Multiplier::one();
|
||||
let mut iterations: u64 = 0;
|
||||
loop {
|
||||
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(fm);
|
||||
let next = runtime_multiplier_update(fm);
|
||||
fm = next;
|
||||
if fm == Multiplier::saturating_from_integer(-1) { break; }
|
||||
if fm == min_multiplier() { break; }
|
||||
iterations += 1;
|
||||
}
|
||||
println!("iteration {}, new fm = {:?}. Weight fee is now zero", iterations, fm);
|
||||
assert!(iterations > 50_000, "This assertion is just a warning; Don't panic. \
|
||||
Current substrate/polkadot node are configured with a _slow adjusting fee_ \
|
||||
mechanism. Hence, it is really unlikely that fees collapse to zero even on an \
|
||||
empty chain in less than at least of couple of thousands of empty blocks. But this \
|
||||
simulation indicates that fees collapsed to zero after {} almost-empty blocks. \
|
||||
Check it",
|
||||
iterations,
|
||||
);
|
||||
assert!(iterations > 533_333);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min_change_per_day() {
|
||||
run_with_system_weight(max(), || {
|
||||
let mut fm = Multiplier::one();
|
||||
// See the example in the doc of `TargetedFeeAdjustment`. are at least 0.234, hence
|
||||
// `fm > 1.234`.
|
||||
for _ in 0..DAYS {
|
||||
let next = runtime_multiplier_update(fm);
|
||||
fm = next;
|
||||
}
|
||||
assert!(fm > Multiplier::saturating_from_rational(1234, 1000));
|
||||
})
|
||||
}
|
||||
|
||||
@@ -196,17 +203,17 @@ mod tests {
|
||||
// almost full. The entire quota of normal transactions is taken.
|
||||
let block_weight = AvailableBlockRatio::get() * max() - 100;
|
||||
|
||||
// Default substrate minimum.
|
||||
let tx_weight = 10_000;
|
||||
// Default substrate weight.
|
||||
let tx_weight = frame_support::weights::constants::ExtrinsicBaseWeight::get();
|
||||
|
||||
run_with_system_weight(block_weight, || {
|
||||
// initial value configured on module
|
||||
let mut fm = Multiplier::default();
|
||||
let mut fm = Multiplier::one();
|
||||
assert_eq!(fm, TransactionPayment::next_fee_multiplier());
|
||||
|
||||
let mut iterations: u64 = 0;
|
||||
loop {
|
||||
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(fm);
|
||||
let next = runtime_multiplier_update(fm);
|
||||
// if no change, panic. This should never happen in this case.
|
||||
if fm == next { panic!("The fee should ever increase"); }
|
||||
fm = next;
|
||||
@@ -230,95 +237,86 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn stateless_weight_mul() {
|
||||
// This test will show that heavy blocks have a weight multiplier greater than 0
|
||||
// and light blocks will have a weight multiplier less than 0.
|
||||
let fm = Multiplier::saturating_from_rational(1, 2);
|
||||
run_with_system_weight(target() / 4, || {
|
||||
// `fee_multiplier_update` is enough as it is the absolute truth value.
|
||||
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
|
||||
assert_eq!(
|
||||
let next = runtime_multiplier_update(fm);
|
||||
assert_eq_error_rate!(
|
||||
next,
|
||||
fee_multiplier_update(target() / 4 ,Multiplier::default())
|
||||
truth_value_update(target() / 4 , fm),
|
||||
Multiplier::from_inner(100),
|
||||
);
|
||||
|
||||
// Light block. Fee is reduced a little.
|
||||
assert!(next < Multiplier::zero())
|
||||
// Light block. Multiplier is reduced a little.
|
||||
assert!(next < fm);
|
||||
});
|
||||
run_with_system_weight(target() / 2, || {
|
||||
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
|
||||
assert_eq!(
|
||||
next,
|
||||
fee_multiplier_update(target() / 2 ,Multiplier::default())
|
||||
);
|
||||
|
||||
// Light block. Fee is reduced a little.
|
||||
assert!(next < Multiplier::zero())
|
||||
run_with_system_weight(target() / 2, || {
|
||||
let next = runtime_multiplier_update(fm);
|
||||
assert_eq_error_rate!(
|
||||
next,
|
||||
truth_value_update(target() / 2 , fm),
|
||||
Multiplier::from_inner(100),
|
||||
);
|
||||
// Light block. Multiplier is reduced a little.
|
||||
assert!(next < fm);
|
||||
|
||||
});
|
||||
run_with_system_weight(target(), || {
|
||||
// ideal. Original fee. No changes.
|
||||
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
|
||||
assert_eq!(next, Multiplier::zero())
|
||||
let next = runtime_multiplier_update(fm);
|
||||
assert_eq_error_rate!(
|
||||
next,
|
||||
truth_value_update(target(), fm),
|
||||
Multiplier::from_inner(100),
|
||||
);
|
||||
// ideal. No changes.
|
||||
assert_eq!(next, fm)
|
||||
});
|
||||
run_with_system_weight(target() * 2, || {
|
||||
// More than ideal. Fee is increased.
|
||||
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
|
||||
assert_eq!(
|
||||
let next = runtime_multiplier_update(fm);
|
||||
assert_eq_error_rate!(
|
||||
next,
|
||||
fee_multiplier_update(target() * 2 ,Multiplier::default())
|
||||
truth_value_update(target() * 2 , fm),
|
||||
Multiplier::from_inner(100),
|
||||
);
|
||||
|
||||
// Heavy block. Fee is increased a little.
|
||||
assert!(next > Multiplier::zero())
|
||||
assert!(next > fm);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stateful_weight_mul_grow_to_infinity() {
|
||||
fn weight_mul_grow_on_big_block() {
|
||||
run_with_system_weight(target() * 2, || {
|
||||
let mut original = Multiplier::default();
|
||||
let mut original = Multiplier::zero();
|
||||
let mut next = Multiplier::default();
|
||||
|
||||
(0..1_000).for_each(|_| {
|
||||
next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(original);
|
||||
assert_eq!(
|
||||
next = runtime_multiplier_update(original);
|
||||
assert_eq_error_rate!(
|
||||
next,
|
||||
fee_multiplier_update(target() * 2, original),
|
||||
truth_value_update(target() * 2, original),
|
||||
Multiplier::from_inner(100),
|
||||
);
|
||||
// must always increase
|
||||
assert!(next > original);
|
||||
assert!(next > original, "{:?} !>= {:?}", next, original);
|
||||
original = next;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stateful_weight_mil_collapse_to_minus_one() {
|
||||
run_with_system_weight(0, || {
|
||||
let mut original = Multiplier::default(); // 0
|
||||
fn weight_mul_decrease_on_small_block() {
|
||||
run_with_system_weight(target() / 2, || {
|
||||
let mut original = Multiplier::saturating_from_rational(1, 2);
|
||||
let mut next;
|
||||
|
||||
// decreases
|
||||
next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(original);
|
||||
assert_eq!(
|
||||
next,
|
||||
fee_multiplier_update(0, original),
|
||||
);
|
||||
assert!(next < original);
|
||||
original = next;
|
||||
|
||||
// keeps decreasing
|
||||
next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(original);
|
||||
assert_eq!(
|
||||
next,
|
||||
fee_multiplier_update(0, original),
|
||||
);
|
||||
assert!(next < original);
|
||||
|
||||
// ... stops going down at -1
|
||||
assert_eq!(
|
||||
TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::saturating_from_integer(-1)),
|
||||
Multiplier::saturating_from_integer(-1)
|
||||
);
|
||||
for _ in 0..100 {
|
||||
// decreases
|
||||
next = runtime_multiplier_update(original);
|
||||
assert!(next < original, "{:?} !<= {:?}", next, original);
|
||||
original = next;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -347,8 +345,8 @@ mod tests {
|
||||
Weight::max_value(),
|
||||
].into_iter().for_each(|i| {
|
||||
run_with_system_weight(i, || {
|
||||
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
|
||||
let truth = fee_multiplier_update(i, Multiplier::default());
|
||||
let next = runtime_multiplier_update(Multiplier::one());
|
||||
let truth = truth_value_update(i, Multiplier::one());
|
||||
assert_eq_error_rate!(truth, next, Multiplier::from_inner(50_000_000));
|
||||
});
|
||||
});
|
||||
@@ -359,7 +357,7 @@ mod tests {
|
||||
.into_iter()
|
||||
.for_each(|i| {
|
||||
run_with_system_weight(i, || {
|
||||
let fm = TargetedFeeAdjustment::<TargetBlockFullness>::convert(max_fm);
|
||||
let fm = runtime_multiplier_update(max_fm);
|
||||
// won't grow. The convert saturates everything.
|
||||
assert_eq!(fm, max_fm);
|
||||
})
|
||||
|
||||
@@ -44,8 +44,8 @@ pub use node_primitives::{AccountId, Signature};
|
||||
use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment};
|
||||
use sp_api::impl_runtime_apis;
|
||||
use sp_runtime::{
|
||||
Permill, Perbill, Perquintill, Percent, PerThing, ApplyExtrinsicResult,
|
||||
impl_opaque_keys, generic, create_runtime_str, ModuleId,
|
||||
Permill, Perbill, Perquintill, Percent, ApplyExtrinsicResult,
|
||||
impl_opaque_keys, generic, create_runtime_str, ModuleId, FixedPointNumber,
|
||||
};
|
||||
use sp_runtime::curve::PiecewiseLinear;
|
||||
use sp_runtime::transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority};
|
||||
@@ -61,6 +61,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};
|
||||
use pallet_contracts_rpc_runtime_api::ContractExecResult;
|
||||
use pallet_session::{historical as pallet_session_historical};
|
||||
use sp_inherents::{InherentData, CheckInherentsResult};
|
||||
@@ -77,7 +78,7 @@ pub use pallet_staking::StakerStatus;
|
||||
|
||||
/// Implementations of some helper traits passed into runtime modules as associated types.
|
||||
pub mod impls;
|
||||
use impls::{CurrencyToVoteHandler, Author, TargetedFeeAdjustment};
|
||||
use impls::{CurrencyToVoteHandler, Author};
|
||||
|
||||
/// Constant values used within the runtime.
|
||||
pub mod constants;
|
||||
@@ -295,23 +296,17 @@ impl pallet_balances::Trait for Runtime {
|
||||
parameter_types! {
|
||||
pub const TransactionByteFee: Balance = 10 * MILLICENTS;
|
||||
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
|
||||
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000);
|
||||
pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128);
|
||||
}
|
||||
|
||||
// for a sane configuration, this should always be less than `AvailableBlockRatio`.
|
||||
const_assert!(
|
||||
TargetBlockFullness::get().deconstruct() <
|
||||
(AvailableBlockRatio::get().deconstruct() as <Perquintill as PerThing>::Inner)
|
||||
* (<Perquintill as PerThing>::ACCURACY / <Perbill as PerThing>::ACCURACY as <Perquintill as PerThing>::Inner)
|
||||
);
|
||||
|
||||
impl pallet_transaction_payment::Trait for Runtime {
|
||||
type Currency = Balances;
|
||||
type OnTransactionPayment = DealWithFees;
|
||||
type TransactionByteFee = TransactionByteFee;
|
||||
// In the Substrate node, a weight of 10_000_000 (smallest non-zero weight)
|
||||
// is mapped to 10_000_000 units of fees, hence:
|
||||
type WeightToFee = IdentityFee<Balance>;
|
||||
type FeeMultiplierUpdate = TargetedFeeAdjustment<TargetBlockFullness>;
|
||||
type FeeMultiplierUpdate =
|
||||
TargetedFeeAdjustment<Self, TargetBlockFullness, AdjustmentVariable, MinimumMultiplier>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -26,6 +26,7 @@ impl sp_runtime::traits::Dispatchable for CallWithDispatchInfo {
|
||||
type Trait = ();
|
||||
type Info = frame_support::weights::DispatchInfo;
|
||||
type PostInfo = frame_support::weights::PostDispatchInfo;
|
||||
|
||||
fn dispatch(self, _origin: Self::Origin)
|
||||
-> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
|
||||
panic!("Do not use dummy implementation for dispatch.");
|
||||
@@ -37,7 +38,7 @@ macro_rules! decl_tests {
|
||||
($test:ty, $ext_builder:ty, $existential_deposit:expr) => {
|
||||
|
||||
use crate::*;
|
||||
use sp_runtime::{FixedPointNumber, FixedI128, traits::{SignedExtension, BadOrigin}};
|
||||
use sp_runtime::{FixedPointNumber, traits::{SignedExtension, BadOrigin}};
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, assert_err,
|
||||
traits::{
|
||||
@@ -45,7 +46,7 @@ macro_rules! decl_tests {
|
||||
Currency, ReservableCurrency, ExistenceRequirement::AllowDeath, StoredMap
|
||||
}
|
||||
};
|
||||
use pallet_transaction_payment::ChargeTransactionPayment;
|
||||
use pallet_transaction_payment::{ChargeTransactionPayment, Multiplier};
|
||||
use frame_system::RawOrigin;
|
||||
|
||||
const ID_1: LockIdentifier = *b"1 ";
|
||||
@@ -166,7 +167,7 @@ macro_rules! decl_tests {
|
||||
.monied(true)
|
||||
.build()
|
||||
.execute_with(|| {
|
||||
pallet_transaction_payment::NextFeeMultiplier::put(FixedI128::saturating_from_integer(1));
|
||||
pallet_transaction_payment::NextFeeMultiplier::put(Multiplier::saturating_from_integer(1));
|
||||
Balances::set_lock(ID_1, &1, 10, WithdrawReason::Reserve.into());
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 1, AllowDeath),
|
||||
|
||||
@@ -570,7 +570,7 @@ decl_module! {
|
||||
/// A dispatch that will fill the block weight up to the given ratio.
|
||||
// TODO: This should only be available for testing, rather than in general usage, but
|
||||
// that's not possible at present (since it's within the decl_module macro).
|
||||
#[weight = (*_ratio * T::MaximumBlockWeight::get(), DispatchClass::Operational)]
|
||||
#[weight = *_ratio * T::MaximumBlockWeight::get()]
|
||||
fn fill_block(origin, _ratio: Perbill) {
|
||||
ensure_root(origin)?;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
|
||||
serde = { version = "1.0.101", optional = true }
|
||||
sp-std = { version = "2.0.0-rc3", default-features = false, path = "../../primitives/std" }
|
||||
sp-runtime = { version = "2.0.0-rc3", default-features = false, path = "../../primitives/runtime" }
|
||||
frame-support = { version = "2.0.0-rc3", default-features = false, path = "../support" }
|
||||
@@ -29,6 +30,7 @@ sp-storage = { version = "2.0.0-rc3", path = "../../primitives/storage" }
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"serde",
|
||||
"codec/std",
|
||||
"sp-std/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
@@ -44,7 +44,7 @@ use frame_support::{
|
||||
dispatch::DispatchResult,
|
||||
};
|
||||
use sp_runtime::{
|
||||
FixedI128, FixedPointNumber, FixedPointOperand,
|
||||
FixedU128, FixedPointNumber, FixedPointOperand, Perquintill, RuntimeDebug,
|
||||
transaction_validity::{
|
||||
TransactionPriority, ValidTransaction, InvalidTransaction, TransactionValidityError,
|
||||
TransactionValidity,
|
||||
@@ -57,13 +57,125 @@ use sp_runtime::{
|
||||
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
|
||||
|
||||
/// Fee multiplier.
|
||||
pub type Multiplier = FixedI128;
|
||||
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;
|
||||
|
||||
/// 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
|
||||
/// be called on `on_finalize` of a block, prior to potentially cleaning the weight data from the
|
||||
/// system module.
|
||||
///
|
||||
/// given:
|
||||
/// s = previous block weight
|
||||
/// s'= ideal block weight
|
||||
/// m = maximum block weight
|
||||
/// diff = (s - s')/m
|
||||
/// v = 0.00001
|
||||
/// t1 = (v * diff)
|
||||
/// t2 = (v * diff)^2 / 2
|
||||
/// then:
|
||||
/// next_multiplier = prev_multiplier * (1 + t1 + t2)
|
||||
///
|
||||
/// Where `(s', v)` must be given as the `Get` implementation of the `T` generic type. Moreover, `M`
|
||||
/// must provide the minimum allowed value for the multiplier. Note that a runtime should ensure
|
||||
/// with tests that the combination of this `M` and `V` is not such that the multiplier can drop to
|
||||
/// zero and never recover.
|
||||
///
|
||||
/// note that `s'` is interpreted as a portion in the _normal transaction_ capacity of the block.
|
||||
/// For example, given `s' == 0.25` and `AvailableBlockRatio = 0.75`, then the target fullness is
|
||||
/// _0.25 of the normal capacity_ and _0.1875 of the entire block_.
|
||||
///
|
||||
/// This implementation implies the bound:
|
||||
/// - `v ≤ p / k * (s − s')`
|
||||
/// - or, solving for `p`: `p >= v * k * (s - s')`
|
||||
///
|
||||
/// where `p` is the amount of change over `k` blocks.
|
||||
///
|
||||
/// Hence:
|
||||
/// - in a fully congested chain: `p >= v * k * (1 - s')`.
|
||||
/// - in an empty chain: `p >= v * k * (-s')`.
|
||||
///
|
||||
/// For example, when all blocks are full and there are 28800 blocks per day (default in `substrate-node`)
|
||||
/// and v == 0.00001, s' == 0.1875, we'd have:
|
||||
///
|
||||
/// p >= 0.00001 * 28800 * 0.8125
|
||||
/// p >= 0.234
|
||||
///
|
||||
/// Meaning that fees can change by around ~23% per day, given extreme congestion.
|
||||
///
|
||||
/// More info can be found at:
|
||||
/// https://w3f-research.readthedocs.io/en/latest/polkadot/Token%20Economics.html
|
||||
pub struct TargetedFeeAdjustment<T, S, V, M>(sp_std::marker::PhantomData<(T, S, V, M)>);
|
||||
|
||||
impl<T, S, V, M> Convert<Multiplier, Multiplier> for TargetedFeeAdjustment<T, S, V, M>
|
||||
where T: frame_system::Trait, S: Get<Perquintill>, V: Get<Multiplier>, M: Get<Multiplier>,
|
||||
{
|
||||
fn convert(previous: Multiplier) -> Multiplier {
|
||||
// Defensive only. The multiplier in storage should always be at most positive. Nonetheless
|
||||
// we recover here in case of errors, because any value below this would be stale and can
|
||||
// never change.
|
||||
let min_multiplier = M::get();
|
||||
let previous = previous.max(min_multiplier);
|
||||
|
||||
// the computed ratio is only among the normal class.
|
||||
let normal_max_weight =
|
||||
<T as frame_system::Trait>::AvailableBlockRatio::get() *
|
||||
<T as frame_system::Trait>::MaximumBlockWeight::get();
|
||||
let normal_block_weight =
|
||||
<frame_system::Module<T>>::block_weight()
|
||||
.get(frame_support::weights::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;
|
||||
|
||||
// determines if the first_term is positive
|
||||
let positive = block_weight >= target_weight;
|
||||
let diff_abs = block_weight.max(target_weight) - block_weight.min(target_weight);
|
||||
|
||||
// defensive only, a test case assures that the maximum weight diff can fit in Multiplier
|
||||
// without any saturation.
|
||||
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 {
|
||||
// Defensive-only: first_term > second_term. Safe subtraction.
|
||||
let negative = first_term.saturating_sub(second_term).saturating_mul(previous);
|
||||
previous.saturating_sub(negative).max(min_multiplier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage releases of the module.
|
||||
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)]
|
||||
enum Releases {
|
||||
/// Original version of the module.
|
||||
V1Ancient,
|
||||
/// One that bumps the usage to FixedU128 from FixedI128.
|
||||
V2,
|
||||
}
|
||||
|
||||
impl Default for Releases {
|
||||
fn default() -> Self {
|
||||
Releases::V1Ancient
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Trait: frame_system::Trait {
|
||||
/// The currency type in which fees will be paid.
|
||||
type Currency: Currency<Self::AccountId> + Send + Sync;
|
||||
@@ -85,7 +197,9 @@ pub trait Trait: frame_system::Trait {
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as TransactionPayment {
|
||||
pub NextFeeMultiplier get(fn next_fee_multiplier): Multiplier = Multiplier::from_inner(0);
|
||||
pub NextFeeMultiplier get(fn next_fee_multiplier): Multiplier = Multiplier::saturating_from_integer(1);
|
||||
|
||||
StorageVersion build(|_: &GenesisConfig| Releases::V2): Releases;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +217,51 @@ decl_module! {
|
||||
*fm = T::FeeMultiplierUpdate::convert(*fm);
|
||||
});
|
||||
}
|
||||
|
||||
fn integrity_test() {
|
||||
// given weight == u64, we build multipliers from `diff` of two weight values, which can
|
||||
// at most be MaximumBlockWeight. Make sure that this can fit in a multiplier without
|
||||
// loss.
|
||||
use sp_std::convert::TryInto;
|
||||
assert!(
|
||||
<Multiplier as sp_runtime::traits::Bounded>::max_value() >=
|
||||
Multiplier::checked_from_integer(
|
||||
<T as frame_system::Trait>::MaximumBlockWeight::get().try_into().unwrap()
|
||||
).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
use frame_support::migration::take_storage_value;
|
||||
use sp_std::convert::TryInto;
|
||||
use frame_support::debug::native::error;
|
||||
|
||||
type OldMultiplier = sp_runtime::FixedI128;
|
||||
type OldInner = <OldMultiplier as FixedPointNumber>::Inner;
|
||||
type Inner = <Multiplier as FixedPointNumber>::Inner;
|
||||
|
||||
if let Releases::V1Ancient = StorageVersion::get() {
|
||||
StorageVersion::put(Releases::V2);
|
||||
|
||||
if let Some(old) = take_storage_value::<OldMultiplier>(
|
||||
b"TransactionPayment",
|
||||
b"NextFeeMultiplier",
|
||||
&[],
|
||||
) {
|
||||
let inner = old.into_inner();
|
||||
let new_inner = <OldInner as TryInto<Inner>>::try_into(inner)
|
||||
.unwrap_or_default();
|
||||
let new = Multiplier::from_inner(new_inner);
|
||||
NextFeeMultiplier::put(new);
|
||||
T::DbWeight::get().reads_writes(1, 1)
|
||||
} else {
|
||||
error!("transaction-payment migration failed.");
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
} else {
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +316,7 @@ impl<T: Trait> Module<T> where
|
||||
/// the minimum fee for a transaction to be included in a block.
|
||||
///
|
||||
/// ```ignore
|
||||
/// inclusion_fee = base_fee + targeted_fee_adjustment * (len_fee + weight_fee);
|
||||
/// inclusion_fee = base_fee + len_fee + [targeted_fee_adjustment * weight_fee];
|
||||
/// final_fee = inclusion_fee + tip;
|
||||
/// ```
|
||||
pub fn compute_fee(
|
||||
@@ -194,16 +353,21 @@ impl<T: Trait> Module<T> where
|
||||
if pays_fee == Pays::Yes {
|
||||
let len = <BalanceOf<T>>::from(len);
|
||||
let per_byte = T::TransactionByteFee::get();
|
||||
let len_fee = per_byte.saturating_mul(len);
|
||||
let unadjusted_weight_fee = Self::weight_to_fee(weight);
|
||||
|
||||
// the adjustable part of the fee
|
||||
let adjustable_fee = len_fee.saturating_add(unadjusted_weight_fee);
|
||||
let targeted_fee_adjustment = NextFeeMultiplier::get();
|
||||
let adjusted_fee = targeted_fee_adjustment.saturating_mul_acc_int(adjustable_fee);
|
||||
// length fee. this is not adjusted.
|
||||
let fixed_len_fee = per_byte.saturating_mul(len);
|
||||
|
||||
// the adjustable part of the fee.
|
||||
let unadjusted_weight_fee = Self::weight_to_fee(weight);
|
||||
let multiplier = Self::next_fee_multiplier();
|
||||
// final adjusted weight fee.
|
||||
let adjusted_weight_fee = multiplier.saturating_mul_int(unadjusted_weight_fee);
|
||||
|
||||
let base_fee = Self::weight_to_fee(T::ExtrinsicBaseWeight::get());
|
||||
base_fee.saturating_add(adjusted_fee).saturating_add(tip)
|
||||
base_fee
|
||||
.saturating_add(fixed_len_fee)
|
||||
.saturating_add(adjusted_weight_fee)
|
||||
.saturating_add(tip)
|
||||
} else {
|
||||
tip
|
||||
}
|
||||
@@ -213,12 +377,12 @@ impl<T: Trait> Module<T> where
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Compute the fee for the specified weight.
|
||||
///
|
||||
/// This fee is already adjusted by the per block fee adjustment factor and is therefore
|
||||
/// the share that the weight contributes to the overall fee of a transaction.
|
||||
/// This fee is already adjusted by the per block fee adjustment factor and is therefore the
|
||||
/// share that the weight contributes to the overall fee of a transaction.
|
||||
///
|
||||
/// This function is generic in order to supply the contracts module with a way
|
||||
/// to calculate the gas price. The contracts module is not able to put the necessary
|
||||
/// `BalanceOf<T>` contraints on its trait. This function is not to be used by this module.
|
||||
/// This function is generic in order to supply the contracts module with a way to calculate the
|
||||
/// gas price. The contracts module is not able to put the necessary `BalanceOf<T>` constraints
|
||||
/// on its trait. This function is not to be used by this module.
|
||||
pub fn weight_to_fee_with_adjustment<Balance>(weight: Weight) -> Balance where
|
||||
Balance: UniqueSaturatedFrom<u128>
|
||||
{
|
||||
@@ -576,6 +740,37 @@ mod tests {
|
||||
PostDispatchInfo { actual_weight: None, }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_to_v2_works() {
|
||||
use sp_runtime::FixedI128;
|
||||
use frame_support::traits::OnRuntimeUpgrade;
|
||||
|
||||
let with_old_multiplier = |mul: FixedI128, expected: FixedU128| {
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
frame_support::migration::put_storage_value(
|
||||
b"TransactionPayment",
|
||||
b"NextFeeMultiplier",
|
||||
&[],
|
||||
mul,
|
||||
);
|
||||
|
||||
assert_eq!(StorageVersion::get(), Releases::V1Ancient);
|
||||
|
||||
TransactionPayment::on_runtime_upgrade();
|
||||
|
||||
assert_eq!(StorageVersion::get(), Releases::V2);
|
||||
assert_eq!(NextFeeMultiplier::get(), expected);
|
||||
})
|
||||
};
|
||||
|
||||
with_old_multiplier(FixedI128::saturating_from_integer(-1), FixedU128::zero());
|
||||
with_old_multiplier(FixedI128::saturating_from_rational(-1, 2), FixedU128::zero());
|
||||
with_old_multiplier(
|
||||
FixedI128::saturating_from_rational(1, 2),
|
||||
FixedU128::saturating_from_rational(1, 2),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_extension_transaction_payment_work() {
|
||||
ExtBuilder::default()
|
||||
@@ -620,21 +815,21 @@ mod tests {
|
||||
.execute_with(||
|
||||
{
|
||||
let len = 10;
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2));
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
|
||||
|
||||
let pre = ChargeTransactionPayment::<Runtime>::from(5 /* tipped */)
|
||||
.pre_dispatch(&2, CALL, &info_from_weight(100), len)
|
||||
.unwrap();
|
||||
// 5 base fee, 3/2 * 10 byte fee, 3/2 * 100 weight fee, 5 tip
|
||||
assert_eq!(Balances::free_balance(2), 200 - 5 - 15 - 150 - 5);
|
||||
// 5 base fee, 10 byte fee, 3/2 * 100 weight fee, 5 tip
|
||||
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()
|
||||
);
|
||||
// 75 (3/2 of the returned 50 units of weight ) is refunded
|
||||
assert_eq!(Balances::free_balance(2), 200 - 5 - 15 - 75 - 5);
|
||||
// 75 (3/2 of the returned 50 units of weight) is refunded
|
||||
assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 75 - 5);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -708,7 +903,7 @@ mod tests {
|
||||
.execute_with(||
|
||||
{
|
||||
// all fees should be x1.5
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2));
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
|
||||
let len = 10;
|
||||
|
||||
assert!(
|
||||
@@ -716,7 +911,14 @@ mod tests {
|
||||
.pre_dispatch(&1, CALL, &info_from_weight(3), len)
|
||||
.is_ok()
|
||||
);
|
||||
assert_eq!(Balances::free_balance(1), 100 - 10 - 5 - (10 + 3) * 3 / 2);
|
||||
assert_eq!(
|
||||
Balances::free_balance(1),
|
||||
100 // original
|
||||
- 10 // tip
|
||||
- 5 // base
|
||||
- 10 // len
|
||||
- (3 * 3 / 2) // adjusted weight
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -736,7 +938,7 @@ mod tests {
|
||||
.execute_with(||
|
||||
{
|
||||
// all fees should be x1.5
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2));
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
|
||||
|
||||
assert_eq!(
|
||||
TransactionPayment::query_info(xt, len),
|
||||
@@ -745,10 +947,8 @@ mod tests {
|
||||
class: info.class,
|
||||
partial_fee:
|
||||
5 * 2 /* base * weight_fee */
|
||||
+ (
|
||||
len as u64 /* len * 1 */
|
||||
+ info.weight.min(MaximumBlockWeight::get()) as u64 * 2 /* weight * weight_to_fee */
|
||||
) * 3 / 2
|
||||
+ len as u64 /* len * 1 */
|
||||
+ info.weight.min(MaximumBlockWeight::get()) as u64 * 2 * 3 / 2 /* weight */
|
||||
},
|
||||
);
|
||||
|
||||
@@ -765,7 +965,7 @@ mod tests {
|
||||
.execute_with(||
|
||||
{
|
||||
// Next fee multiplier is zero
|
||||
assert_eq!(NextFeeMultiplier::get(), Multiplier::saturating_from_integer(0));
|
||||
assert_eq!(NextFeeMultiplier::get(), Multiplier::one());
|
||||
|
||||
// Tip only, no fees works
|
||||
let dispatch_info = DispatchInfo {
|
||||
@@ -804,8 +1004,8 @@ mod tests {
|
||||
.build()
|
||||
.execute_with(||
|
||||
{
|
||||
// Add a next fee multiplier
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2)); // = 1/2 = .5
|
||||
// Add a next fee multiplier. Fees will be x3/2.
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(3, 2));
|
||||
// Base fee is unaffected by multiplier
|
||||
let dispatch_info = DispatchInfo {
|
||||
weight: 0,
|
||||
@@ -821,10 +1021,10 @@ mod tests {
|
||||
pays_fee: Pays::Yes,
|
||||
};
|
||||
// 123 weight, 456 length, 100 base
|
||||
// adjustable fee = (123 * 1) + (456 * 10) = 4683
|
||||
// adjusted fee = (4683 * .5) + 4683 = 7024.5 -> 7024
|
||||
// final fee = 100 + 7024 + 789 tip = 7913
|
||||
assert_eq!(Module::<Runtime>::compute_fee(456, &dispatch_info, 789), 7913);
|
||||
assert_eq!(
|
||||
Module::<Runtime>::compute_fee(456, &dispatch_info, 789),
|
||||
100 + (3 * 123 / 2) + 4560 + 789,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -837,9 +1037,10 @@ mod tests {
|
||||
.build()
|
||||
.execute_with(||
|
||||
{
|
||||
// Add a next fee multiplier
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(-1, 2)); // = -1/2 = -.5
|
||||
// Base fee is unaffected by multiplier
|
||||
// Add a next fee multiplier. All fees will be x1/2.
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 2));
|
||||
|
||||
// Base fee is unaffected by multiplier.
|
||||
let dispatch_info = DispatchInfo {
|
||||
weight: 0,
|
||||
class: DispatchClass::Operational,
|
||||
@@ -847,17 +1048,17 @@ mod tests {
|
||||
};
|
||||
assert_eq!(Module::<Runtime>::compute_fee(0, &dispatch_info, 0), 100);
|
||||
|
||||
// Everything works together :)
|
||||
// Everything works together.
|
||||
let dispatch_info = DispatchInfo {
|
||||
weight: 123,
|
||||
class: DispatchClass::Operational,
|
||||
pays_fee: Pays::Yes,
|
||||
};
|
||||
// 123 weight, 456 length, 100 base
|
||||
// adjustable fee = (123 * 1) + (456 * 10) = 4683
|
||||
// adjusted fee = 4683 - (4683 * -.5) = 4683 - 2341.5 = 4683 - 2341 = 2342
|
||||
// final fee = 100 + 2342 + 789 tip = 3231
|
||||
assert_eq!(Module::<Runtime>::compute_fee(456, &dispatch_info, 789), 3231);
|
||||
assert_eq!(
|
||||
Module::<Runtime>::compute_fee(456, &dispatch_info, 789),
|
||||
100 + (123 / 2) + 4560 + 789,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -993,7 +1194,7 @@ mod tests {
|
||||
let len = 10;
|
||||
let tip = 5;
|
||||
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(1, 4));
|
||||
NextFeeMultiplier::put(Multiplier::saturating_from_rational(5, 4));
|
||||
|
||||
let pre = ChargeTransactionPayment::<Runtime>::from(tip)
|
||||
.pre_dispatch(&2, CALL, &info, len)
|
||||
@@ -1007,11 +1208,8 @@ mod tests {
|
||||
let actual_fee = Module::<Runtime>
|
||||
::compute_actual_fee(len as u32, &info, &post_info, tip);
|
||||
|
||||
// 33 weight, 10 length, 7 base
|
||||
// adjustable fee = (33 * 1) + (10 * 1) = 43
|
||||
// adjusted fee = 43 + (43 * .25) = 43 + 10.75 = 43 + 10 = 53
|
||||
// final fee = 7 + 53 + 5 tip = 65
|
||||
assert_eq!(actual_fee, 65);
|
||||
// 33 weight, 10 length, 7 base, 5 tip
|
||||
assert_eq!(actual_fee, 7 + 10 + (33 * 5 / 4) + 5);
|
||||
assert_eq!(refund_based_fee, actual_fee);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -372,6 +372,23 @@ macro_rules! implement_fixed {
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// const version of `FixedPointNumber::from_inner`.
|
||||
pub const fn from_inner(inner: $inner_type) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
pub fn from_fraction(x: f64) -> Self {
|
||||
Self((x * (<Self as FixedPointNumber>::DIV as f64)) as $inner_type)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", test))]
|
||||
pub fn to_fraction(self) -> f64 {
|
||||
self.0 as f64 / <Self as FixedPointNumber>::DIV as f64
|
||||
}
|
||||
}
|
||||
|
||||
impl Saturating for $name {
|
||||
fn saturating_add(self, rhs: Self) -> Self {
|
||||
Self(self.0.saturating_add(rhs.0))
|
||||
|
||||
Reference in New Issue
Block a user