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:
Kian Paimani
2020-06-17 15:20:17 +02:00
committed by GitHub
parent 67c9ac9393
commit 82bdf1a891
10 changed files with 458 additions and 253 deletions
+1
View File
@@ -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",
+24 -31
View File
@@ -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);
});
+6 -6
View File
@@ -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
+148 -150
View File
@@ -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);
})
+8 -13
View File
@@ -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! {
+4 -3
View File
@@ -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),
+1 -1
View File
@@ -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",
+247 -49
View File
@@ -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))