Skip to content

Dynamic Fee Adjustment Crate #161

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions Cargo.lock

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

40 changes: 40 additions & 0 deletions sugondat-chain/pallets/length-fee-adjustment/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[package]
name = "pallet-sugondat-length-fee-adjustment"
version = "0.1.0"
authors = ["Anonymous"]
description = "Pallet for fee Adjustment based on block length and weight"
license = "MIT OR Apache-2.0"
repository = "https://github.com/thrumdev/blobs"
edition = "2021"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false }
scale-info = { version = "2.2.0", default-features = false, features = ["derive"] }
frame-support = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
frame-system = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
pallet-balances = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
sp-weights = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"}
sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"}

[dev-dependencies]
sp-io = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"}
sp-core = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }

[features]
default = [ "std" ]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"sp-runtime/std",
"sp-weights/std",
"scale-info/std",
"sp-arithmetic/std",
"pallet-transaction-payment/std",
"pallet-balances/std",
]
224 changes: 224 additions & 0 deletions sugondat-chain/pallets/length-fee-adjustment/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

/// Currently, the `pallet_transaction_payment` uses the following formula:
///
/// ```ignore
/// inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee];
/// ```
///
/// This formula allows us to update `targeted_fee_adjustment` at the end of each block
/// using `FeeMultiplierUpdate`, this associated type is called within the `on_finalize`
/// function of the `transaction_payment` pallet, with the purpose of converting the existing
/// `targeted_fee_adjustment` to a new one based on network congestion.
///
/// The goal of this pallet is to achieve a modified fee calculation formula:
///
/// ```ignore
/// inclusion_fee = base_fee + [targeted_length_fee_adjustment * length_fee] + [targeted_weight_fee_adjustment * weight_fee];
/// ```
///
/// As you can see, `targeted_fee_adjustment` becomes `targeted_weight_fee_adjustment`,
/// while the behavior remains the same. The side effect is adding the multiplier
/// `targeted_length_fee_adjustment` to `length_fee`. This formula is achievable because the `transaction_payment`
/// pallet uses the `compute_fee_raw` function, which computes the final fee associated with an
/// extrinsic. This function utilizes the associated type `LengthToFee`, which converts the length
/// of an extrinsic to a fee.
///
/// By default, the implementation of `LengthToFee` is a constant multiplication. However, we
/// aim to achieve a dynamic formula that can adapt based on network usage.
/// The following pallet contains an implementation of `LengthToFee`.
///
/// This pallet provides a dynamic update of `targeted_length_fee_adjustment`, inside `on_initialize`, and
/// `targeted_weight_fee_adjustment` implmenting `MultiplierUpdate`. The effective implementation
/// of the formula described above is made possible by using this pallet as associated type in `pallet_transaction_payment`
/// for both `LengthToFee` and `FeeMultiplierUpdate`.
#[frame_support::pallet]
pub mod pallet {

use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use pallet_transaction_payment::{
Multiplier, MultiplierUpdate, OnChargeTransaction, TargetedFeeAdjustment,
};
use sp_runtime::{
traits::{Convert, Get},
FixedPointNumber, Perquintill, SaturatedConversion, Saturating,
};

/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
// `targeted_weight_fee_adjustment` parameters
#[pallet::constant]
type TargetBlockFullness: Get<Perquintill>;
#[pallet::constant]
type AdjustmentVariableBlockFullness: Get<Multiplier>;
#[pallet::constant]
type MinimumMultiplierBlockFullness: Get<Multiplier>;
#[pallet::constant]
type MaximumMultiplierBlockFullness: Get<Multiplier>;

// `targeted_length_fee_adjustment` parameters
#[pallet::constant]
type TransactionByteFee: Get<<<Self as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<Self>>::Balance>;
#[pallet::constant]
type MaximumBlockLength: Get<u32>;
#[pallet::constant]
type AdjustmentVariableBlockSize: Get<Multiplier>;
#[pallet::constant]
type MinimumMultiplierBlockSize: Get<Multiplier>;
#[pallet::constant]
type MaximumMultiplierBlockSize: Get<Multiplier>;
}

#[pallet::pallet]
pub struct Pallet<T>(_);

pub struct NextLengthMultiplierDefualt;
impl Get<Multiplier> for NextLengthMultiplierDefualt {
fn get() -> Multiplier {
Multiplier::saturating_from_integer(1)
}
}

#[pallet::storage]
pub type NextLengthMultiplier<T: Config> =
StorageValue<_, Multiplier, ValueQuery, NextLengthMultiplierDefualt>;

pub struct TargetBlockSizeDefualt;
impl Get<Perquintill> for TargetBlockSizeDefualt {
fn get() -> Perquintill {
Perquintill::from_percent(16) // 0.8MiB
}
}

#[pallet::storage]
pub type TargetBlockSize<T: Config> =
StorageValue<_, Perquintill, ValueQuery, TargetBlockSizeDefualt>;

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
// TODO: implement skip block logic
// https://github.com/thrumdev/blobs/issues/165

// NextLengthMultiplier: 1r + 1w
// TargetBlockSize: 1r
T::DbWeight::get().reads_writes(2, 1)
}

fn on_finalize(_n: BlockNumberFor<T>) {
// update targeted_weight_fee_adjustment,
// contained in NextLengthMultiplier storage item

// This is essentially a copy-paste of the function TargetedFeeAdjustment::convert.
// The main problem is that TargetedFeeAdjustment::convert directly calls the storage to extract the weight
// of the current block, so there is no way to pass the length as an input argument and reuse the function to
// update also the length multiplier.
// Therefore, all the necessary parts taken and properly adapted to update NextLengthMultiplier.

// 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 previous_len_multiplier = NextLengthMultiplier::<T>::get();
let min_multiplier = T::MinimumMultiplierBlockSize::get();
let max_multiplier = T::MaximumMultiplierBlockSize::get();
let previous_len_multiplier = previous_len_multiplier.max(min_multiplier);

// The limiting dimension is the length of all extrinsic
let (normal_limiting_dimension, max_limiting_dimension) = (
<frame_system::Pallet<T>>::all_extrinsics_len().min(T::MaximumBlockLength::get()),
T::MaximumBlockLength::get() as u64,
);

let target_block_size = TargetBlockSize::<T>::get();
let adjustment_variable = T::AdjustmentVariableBlockSize::get();

let target_size = (target_block_size * max_limiting_dimension) as u128;
let block_size = normal_limiting_dimension as u128;

// determines if the first_term is positive
let positive = block_size >= target_size;
let diff_abs = block_size.max(target_size) - block_size.min(target_size);

// 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, max_limiting_dimension.max(1));
let diff_squared = diff.saturating_mul(diff);

let v_squared_2 = adjustment_variable.saturating_mul(adjustment_variable)
/ Multiplier::saturating_from_integer(2);

let first_term = adjustment_variable.saturating_mul(diff);
let second_term = v_squared_2.saturating_mul(diff_squared);

let new_len_multiplier = if positive {
let excess = first_term
.saturating_add(second_term)
.saturating_mul(previous_len_multiplier);
previous_len_multiplier
.saturating_add(excess)
.clamp(min_multiplier, max_multiplier)
} else {
// Defensive-only: first_term > second_term. Safe subtraction.
let negative = first_term
.saturating_sub(second_term)
.saturating_mul(previous_len_multiplier);
previous_len_multiplier
.saturating_sub(negative)
.clamp(min_multiplier, max_multiplier)
};

NextLengthMultiplier::<T>::put(new_len_multiplier);
}
}

impl<T: Config + pallet_transaction_payment::Config> sp_weights::WeightToFee for Pallet<T> {
type Balance = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance;

fn weight_to_fee(weight: &Weight) -> Self::Balance {
// really weird but weight.ref_time will contain the length of the extrinsic
let length_fee = Self::Balance::saturated_from(weight.ref_time())
.saturating_mul(T::TransactionByteFee::get());
let multiplier = NextLengthMultiplier::<T>::get();

// final adjusted length fee
multiplier.saturating_mul_int(length_fee)
}
}

impl<T: Config> Convert<Multiplier, Multiplier> for Pallet<T> {
fn convert(previous_fee_multiplier: Multiplier) -> Multiplier {
TargetedFeeAdjustment::<
T,
T::TargetBlockFullness,
T::AdjustmentVariableBlockFullness,
T::MinimumMultiplierBlockFullness,
T::MaximumMultiplierBlockFullness,
>::convert(previous_fee_multiplier)
}
}

impl<T: Config> MultiplierUpdate for Pallet<T> {
fn min() -> Multiplier {
T::MinimumMultiplierBlockFullness::get()
}
fn max() -> Multiplier {
T::MaximumMultiplierBlockFullness::get()
}
fn target() -> Perquintill {
T::TargetBlockFullness::get()
}
fn variability() -> Multiplier {
T::AdjustmentVariableBlockFullness::get()
}
}
}
Loading