Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: update fee calculation #289

Merged
merged 3 commits into from
Jul 2, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ homepage = 'https://github.com/galacticcouncil/hydradx-node'
license = 'Apache 2.0'
name = 'hydra-dx-runtime'
repository = 'https://github.com/galacticcouncil/hydradx-node'
version = '18.0.0'
version = '19.0.0'

[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
Expand All @@ -24,6 +24,7 @@ version = '2.0.0'
hex-literal = {optional = true, version = '0.3.1'}
serde = {features = ['derive'], optional = true, version = '1.0.101'}
tracing-core = {optional = true, version = '0.1.17'}
smallvec = "1.6.1"

# local dependencies
pallet-asset-registry = {path = '../pallets/asset-registry', default-features = false}
Expand Down Expand Up @@ -90,6 +91,7 @@ sp-staking = {default-features = false, version = '3.0.0'}
sp-std = {default-features = false, version = '3.0.0'}
sp-transaction-pool = {default-features = false, version = '3.0.0'}
sp-version = {default-features = false, version = '3.0.0'}
sp-io = {default-features = false, version = "3.0.0"}

[features]
default = ['std']
Expand Down Expand Up @@ -120,6 +122,7 @@ std = [
'orml-tokens/std',
'orml-traits/std',
'pallet-xyk/std',
'pallet-xyk-rpc-runtime-api/std',
'pallet-claims/std',
'pallet-asset-registry/std',
'pallet-democracy/std',
Expand Down
1 change: 0 additions & 1 deletion runtime/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ mod tests {
// 6s per block
assert_eq!(SECS_PER_BLOCK, 6);
// 1s = 1000ms
pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4);
assert_eq!(MILLISECS_PER_BLOCK / 1000, SECS_PER_BLOCK);
// Extra check for epoch time because changing it bricks the block production and requires regenesis
assert_eq!(EPOCH_DURATION_IN_BLOCKS, 4 * HOURS);
Expand Down
50 changes: 43 additions & 7 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::from_over_into)]

#[cfg(test)]
mod tests;

// Make the WASM binary available.
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
Expand Down Expand Up @@ -59,7 +62,7 @@ pub use frame_support::{
traits::{Filter, KeyOwnerProofSystem, LockIdentifier, Randomness, U128CurrencyToVote},
weights::{
constants::{BlockExecutionWeight, RocksDbWeight, WEIGHT_PER_SECOND},
DispatchClass, IdentityFee, Pays, Weight,
DispatchClass, Pays, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial,
},
PalletId, StorageValue,
};
Expand Down Expand Up @@ -148,7 +151,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("hydra-dx"),
impl_name: create_runtime_str!("hydra-dx"),
authoring_version: 1,
spec_version: 18,
spec_version: 19,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand All @@ -170,6 +173,34 @@ const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
/// We allow for 2 seconds of compute with a 6 second average block time.
pub const MAXIMUM_BLOCK_WEIGHT: Weight = 2 * WEIGHT_PER_SECOND;

use smallvec::smallvec;
pub struct WeightToFee;
impl WeightToFeePolynomial for WeightToFee {
type Balance = Balance;

/// Handles converting a weight scalar to a fee value, based on the scale and granularity of the
/// node's balance type.
///
/// This should typically create a mapping between the following ranges:
/// - [0, MAXIMUM_BLOCK_WEIGHT]
/// - [Balance::min, Balance::max]
///
/// Yet, it can be used for any other sort of change to weight-fee. Some examples being:
/// - Setting it to `0` will essentially disable the weight fee.
/// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged.
fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
// extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT
let p = CENTS; // 100_000_000_000
let q = 10 * Balance::from(ExtrinsicBaseWeight::get()); // 8_079_830_000
smallvec![WeightToFeeCoefficient {
degree: 1,
negative: false,
coeff_frac: Perbill::from_rational(p % q, q),
coeff_integer: p / q, // 12
}]
}
}

/// The version information used to identify this runtime when compiled natively.
#[cfg(feature = "std")]
pub fn native_version() -> NativeVersion {
Expand Down Expand Up @@ -353,18 +384,23 @@ impl pallet_balances::Config for Runtime {
}

parameter_types! {
pub const TransactionByteFee: Balance = 1;
pub const TransactionByteFee: Balance = 10 * MILLICENTS;
pub const MultiPaymentCurrencySetFee: Pays = Pays::No;

/// The portion of the `NORMAL_DISPATCH_RATIO` that we adjust the fees with. Blocks filled less
/// than this will decrease the weight and more will increase.
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000);
/// The adjustment variable of the runtime. Higher values will cause `TargetBlockFullness` to
/// change the fees more rapidly.
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(3, 100_000);
/// Minimum amount of the multiplier. This value cannot be too low. A test case should ensure
/// that combined with `AdjustmentVariable`, we can recover from the minimum.
pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128);
}

impl pallet_transaction_payment::Config for Runtime {
type OnChargeTransaction = MultiCurrencyAdapter<Balances, (), MultiTransactionPayment>;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = IdentityFee<Balance>;
type WeightToFee = WeightToFee;
type FeeMultiplierUpdate = TargetedFeeAdjustment<Self, TargetBlockFullness, AdjustmentVariable, MinimumMultiplier>;
}

Expand All @@ -375,7 +411,7 @@ impl pallet_transaction_multi_payment::Config for Runtime {
type AMMPool = XYK;
type WeightInfo = pallet_transaction_multi_payment::weights::HydraWeight<Runtime>;
type WithdrawFeeForSetCurrency = MultiPaymentCurrencySetFee;
type WeightToFee = IdentityFee<Balance>;
type WeightToFee = WeightToFee;
}

impl pallet_genesis_history::Config for Runtime {}
Expand Down
100 changes: 100 additions & 0 deletions runtime/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Tests for the HydraDX Runtime Configuration

use crate::*;
use codec::Encode;
use frame_support::storage::StorageValue;
use frame_support::weights::{DispatchClass, GetDispatchInfo, WeightToFeePolynomial};
use sp_runtime::traits::Convert;
use sp_runtime::FixedPointNumber;

#[test]
fn full_block_cost() {
let max_bytes = *BlockLength::get().max.get(DispatchClass::Normal) as u128;
let length_fee = max_bytes * &TransactionByteFee::get();
assert_eq!(length_fee, 3_932_160_000_000_000);

let max_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap_or(1);
let weight_fee = WeightToFee::calc(&max_weight);
assert_eq!(weight_fee, 18_564_747_030_000);

let target_fee = 395 * DOLLARS + 725_555_013_000;
assert_eq!(ExtrinsicBaseWeight::get() as u128 + length_fee + weight_fee, target_fee);
}

#[test]
// This function tests that the fee for `ExtrinsicBaseWeight` of weight is correct
fn extrinsic_base_fee_is_correct() {
// `ExtrinsicBaseWeight` should cost 1/10 of a CENT
let base_fee = WeightToFee::calc(&ExtrinsicBaseWeight::get());
let base_fee_expected = CENTS / 10;
assert!(base_fee.max(base_fee_expected) - base_fee.min(base_fee_expected) < MILLICENTS);
}

#[test]
#[ignore]
fn transfer_cost() {
let call = <pallet_balances::Call<Runtime>>::transfer(Default::default(), Default::default());
let info = call.get_dispatch_info();
// convert to outer call
let call = Call::Balances(call);
let len = call.using_encoded(|e| e.len()) as u32;

let mut ext = sp_io::TestExternalities::new_empty();
ext.execute_with(|| {
pallet_transaction_payment::NextFeeMultiplier::put(Multiplier::saturating_from_integer(1));
let fee_raw = TransactionPayment::compute_fee_details(len, &info, 0);
let fee = fee_raw.final_fee();
println!(
"len = {:?} // weight = {:?} // base fee = {:?} // len fee = {:?} // adjusted weight_fee = {:?} // full transfer fee = {:?}\n",
len,
info.weight,
fee_raw.inclusion_fee.clone().unwrap().base_fee,
fee_raw.inclusion_fee.clone().unwrap().len_fee,
fee_raw.inclusion_fee.unwrap().adjusted_weight_fee,
fee,
);
});
}

fn run_with_system_weight<F>(w: Weight, mut assertions: F)
where
F: FnMut() -> (),
{
let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default()
.build_storage::<Runtime>()
.unwrap()
.into();
t.execute_with(|| {
System::set_block_consumed_resources(w, 0);
assertions()
});
}

#[test]
fn multiplier_can_grow_from_zero() {
let minimum_multiplier = MinimumMultiplier::get();
let target = TargetBlockFullness::get() * BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap();
// 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 = TargetedFeeAdjustment::<Runtime, TargetBlockFullness, AdjustmentVariable, MinimumMultiplier>::convert(minimum_multiplier);
assert!(next > minimum_multiplier, "{:?} !>= {:?}", next, minimum_multiplier);
})
}

#[test]
#[ignore]
fn multiplier_growth_simulator() {
// calculate the value of the fee multiplier after one hour of operation with fully loaded blocks
let mut multiplier = Multiplier::saturating_from_integer(1);
let block_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap();
for _block_num in 1..=24 * HOURS {
run_with_system_weight(block_weight, || {
let next = TargetedFeeAdjustment::<Runtime, TargetBlockFullness, AdjustmentVariable, MinimumMultiplier>::convert(multiplier);
// ensure that it is growing as well.
assert!(next > multiplier, "{:?} !>= {:?}", next, multiplier);
multiplier = next;
});
}
println!("multiplier = {:?}", multiplier);
}