Skip to content

Commit b083445

Browse files
gabriele-0201rphmeier
authored andcommitted
add module fee_adjustment
1 parent ed770d5 commit b083445

File tree

3 files changed

+202
-197
lines changed

3 files changed

+202
-197
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
use crate::constants::kusama::currency::MILLICENTS;
2+
use frame_support::parameter_types;
3+
use pallet_transaction_payment::{Multiplier, MultiplierUpdate, TargetedFeeAdjustment};
4+
use sp_runtime::{
5+
traits::{Bounded, Convert},
6+
FixedPointNumber, Perquintill, SaturatedConversion, Saturating,
7+
};
8+
use sp_weights::Weight;
9+
use sugondat_primitives::{Balance, MAXIMUM_BLOCK_LENGTH};
10+
11+
parameter_types! {
12+
/// Relay Chain `TransactionByteFee` / 10
13+
pub const TransactionByteFee: Balance = MILLICENTS;
14+
15+
// parameters used by BlobsFeeAdjustment
16+
// to update NextFeeMultiplier and NextLengthMultiplier
17+
//
18+
// Common constants used in all runtimes for SlowAdjustingFeeUpdate
19+
/// The portion of the `NORMAL_DISPATCH_RATIO` that we adjust the fees with. Blocks filled less
20+
/// than this will decrease the weight and more will increase.
21+
pub storage TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
22+
23+
/// The adjustment variable of the runtime. Higher values will cause `TargetBlockFullness` to
24+
/// change the fees more rapidly.
25+
pub AdjustmentVariableBlockFullness: Multiplier = Multiplier::saturating_from_rational(75, 1_000_000);
26+
/// that combined with `AdjustmentVariable`, we can recover from the minimum.
27+
/// See `multiplier_can_grow_from_zero`.
28+
pub MinimumMultiplierBlockFullness: Multiplier = Multiplier::saturating_from_rational(1, 10u128);
29+
/// The maximum amount of the multiplier.
30+
pub MaximumMultiplierBlockFullness: Multiplier = Bounded::max_value();
31+
32+
33+
pub storage NextLengthMultiplier: Multiplier = Multiplier::saturating_from_integer(1);
34+
pub storage TargetBlockSize: Perquintill = Perquintill::from_percent(16); // 0.8MiB
35+
// TODO: update those value accordingly with https://github.com/thrumdev/blobs/issues/16
36+
pub AdjustmentVariableBlockSize: Multiplier = Multiplier::saturating_from_rational(75, 1_000_000);
37+
pub MinimumMultiplierBlockSize: Multiplier = Multiplier::saturating_from_rational(1, 10u128);
38+
pub MaximumMultiplierBlockSize: Multiplier = Bounded::max_value();
39+
}
40+
41+
/// Currently pallet_transaction_payment use the following formula:
42+
///
43+
/// ```ignore
44+
/// inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee];
45+
/// ```
46+
///
47+
/// Letting us able to update `targeted_fee_adjustment` at the end of each block
48+
/// thanks to `FeeMultiplierUpdate`, this associated type is called inside the `on_finalize`
49+
/// of the transaction_payment pallet with the aim of converting the before `targeted_fee_adjustment`
50+
/// to a new one based on the congestion of the network
51+
///
52+
/// What this struct does is this PLUS a side effect, the goal is to reach a different formula to
53+
/// calculate fees:
54+
///
55+
/// ```ignore
56+
/// inclusion_fee = base_fee + [targeted_length_fee_adjustment * length_fee] + [targeted_weight_fee_adjustment * weight_fee];
57+
/// ```
58+
///
59+
/// As you can see `targeted_fee_adjustment` becomes `targeted_weight_fee_adjustment` but the behavior
60+
/// remains the same, the side effect is the changing to the value `targeted_length_fee_adjustment`,
61+
/// this formula is achievable because inside pallet_transaction_payment the function `compute_fee_raw`
62+
/// that just computes the final fee associated with an extrinsic uses the associated type `LengthToFee`
63+
/// that converts the length of an extrinsic to a fee.
64+
///
65+
/// By default the implementation is a constant multiplication but we want to achieve a dynamic formula
66+
/// that can adapt based on the usage of the network, this can't solely be done by this struct but needs
67+
/// to be bundled with a custom implementation of `LengthToFee`.
68+
///
69+
/// This struct ONLY provide a dynamic update of `targeted_length_fee_adjustment` and `targeted_weight_fee_adjustment`
70+
/// based on the congestion and usage of the blocks, while the formula si effectively implemented like
71+
/// explained above only thanks to `LengthToFee`
72+
pub struct BlobsFeeAdjustment<T: frame_system::Config>(core::marker::PhantomData<T>);
73+
74+
impl<T: frame_system::Config> Convert<Multiplier, Multiplier> for BlobsFeeAdjustment<T>
75+
where
76+
T: frame_system::Config,
77+
{
78+
/// This function should be a pure function used to update NextFeeMultiplier
79+
/// but will also has the side effect of update NextLengthMultiplier
80+
fn convert(previous_fee_multiplier: Multiplier) -> Multiplier {
81+
// Update NextLengthMultiplier
82+
83+
// To update the value will be used the same formula as TargetedFeeAdjustment,
84+
// described here: https://research.web3.foundation/Polkadot/overview/token-economics#2-slow-adjusting-mechanism
85+
//
86+
// so this is mainly a copy paste of that function because it works on normalized mesurments,
87+
// so if it is ref_time, proof_size or length of the extrinsic the mutliplier will be evaluated properly.
88+
// The main problem is that TargetedFeeAdjustment::convert uses directly a call to the storage to extract
89+
// the weight of the current block so there is no way to pass the length as input argument,
90+
// here I will copy paste all the needed part to update properly NextLengthMultiplier
91+
92+
// Defensive only. The multiplier in storage should always be at most positive. Nonetheless
93+
// we recover here in case of errors, because any value below this would be stale and can
94+
// never change.
95+
96+
let previous_len_multiplier = NextLengthMultiplier::get();
97+
let min_multiplier = MinimumMultiplierBlockSize::get();
98+
let max_multiplier = MaximumMultiplierBlockSize::get();
99+
let previous_len_multiplier = previous_len_multiplier.max(min_multiplier);
100+
101+
// Pick the limiting dimension. (from TargetedFeeAdjustment::convert)
102+
//
103+
// In this case it is the length of all extrinsic, always
104+
let (normal_limiting_dimension, max_limiting_dimension) = (
105+
<frame_system::Pallet<T>>::all_extrinsics_len(),
106+
MAXIMUM_BLOCK_LENGTH as u64,
107+
);
108+
109+
let target_block_size = TargetBlockSize::get();
110+
let adjustment_variable = AdjustmentVariableBlockSize::get();
111+
112+
let target_size = (target_block_size * max_limiting_dimension) as u128;
113+
let block_size = normal_limiting_dimension as u128;
114+
115+
// determines if the first_term is positive
116+
let positive = block_size >= target_size;
117+
let diff_abs = block_size.max(target_size) - block_size.min(target_size);
118+
119+
// defensive only, a test case assures that the maximum weight diff can fit in Multiplier
120+
// without any saturation.
121+
let diff = Multiplier::saturating_from_rational(diff_abs, max_limiting_dimension.max(1));
122+
let diff_squared = diff.saturating_mul(diff);
123+
124+
let v_squared_2 = adjustment_variable.saturating_mul(adjustment_variable)
125+
/ Multiplier::saturating_from_integer(2);
126+
127+
let first_term = adjustment_variable.saturating_mul(diff);
128+
let second_term = v_squared_2.saturating_mul(diff_squared);
129+
130+
let new_len_multiplier = if positive {
131+
let excess = first_term
132+
.saturating_add(second_term)
133+
.saturating_mul(previous_len_multiplier);
134+
previous_len_multiplier
135+
.saturating_add(excess)
136+
.clamp(min_multiplier, max_multiplier)
137+
} else {
138+
// Defensive-only: first_term > second_term. Safe subtraction.
139+
let negative = first_term
140+
.saturating_sub(second_term)
141+
.saturating_mul(previous_len_multiplier);
142+
previous_len_multiplier
143+
.saturating_sub(negative)
144+
.clamp(min_multiplier, max_multiplier)
145+
};
146+
147+
NextLengthMultiplier::set(&new_len_multiplier);
148+
149+
// Update NextFeeMultiplier
150+
//
151+
// Here is the tricky part, this method return the new value associated with
152+
// NextFeeMultiplier (in the old fashion) because weight dynamic adjustment is battle tested
153+
// while previously have updated the `NextLengthMultiplier` used in `LengthToWeight`
154+
TargetedFeeAdjustment::<
155+
T,
156+
TargetBlockFullness,
157+
AdjustmentVariableBlockFullness,
158+
MinimumMultiplierBlockFullness,
159+
MaximumMultiplierBlockFullness,
160+
>::convert(previous_fee_multiplier)
161+
}
162+
}
163+
164+
impl<T: frame_system::Config> MultiplierUpdate for BlobsFeeAdjustment<T> {
165+
fn min() -> Multiplier {
166+
MinimumMultiplierBlockFullness::get()
167+
}
168+
fn max() -> Multiplier {
169+
MaximumMultiplierBlockFullness::get()
170+
}
171+
fn target() -> Perquintill {
172+
TargetBlockFullness::get()
173+
}
174+
fn variability() -> Multiplier {
175+
AdjustmentVariableBlockFullness::get()
176+
}
177+
}
178+
179+
pub struct BlobsLengthToFee<T: frame_system::Config>(core::marker::PhantomData<T>);
180+
181+
impl<T: frame_system::Config> sp_weights::WeightToFee for BlobsLengthToFee<T> {
182+
type Balance = Balance;
183+
184+
fn weight_to_fee(weight: &Weight) -> Self::Balance {
185+
// really weird but weight.ref_time will contain the length of the extrinsic
186+
let length_fee = Self::Balance::saturated_from(weight.ref_time())
187+
.saturating_mul(TransactionByteFee::get());
188+
let multiplier = NextLengthMultiplier::get();
189+
190+
// final adjusted length fee
191+
multiplier.saturating_mul_int(length_fee)
192+
}
193+
}

0 commit comments

Comments
 (0)