Skip to content

Commit efcf6b6

Browse files
committed
extract fee adjustment into new crate
1 parent 73a9893 commit efcf6b6

File tree

6 files changed

+914
-8
lines changed

6 files changed

+914
-8
lines changed

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "dynamic-fee-adjustment"
3+
version = "0.1.0"
4+
authors = ["Anonymous"]
5+
description = "Fee Adjustment based on block length and weight"
6+
license = "MIT OR Apache-2.0"
7+
repository = "https://github.com/thrumdev/blobs"
8+
edition = "2021"
9+
10+
11+
[dependencies]
12+
frame-support = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
13+
frame-system = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
14+
pallet-transaction-payment = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
15+
sp-runtime = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0" }
16+
sp-weights = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"}
17+
sp-arithmetic = { git = "https://github.com/paritytech/polkadot-sdk", default-features = false, branch = "release-polkadot-v1.4.0"}
18+
19+
[features]
20+
default = [ "std" ]
21+
std = [
22+
"frame-support/std",
23+
"frame-system/std",
24+
"sp-runtime/std",
25+
"sp-weights/std",
26+
"sp-arithmetic/std",
27+
"pallet-transaction-payment/std",
28+
]
29+
30+
#runtime-benchmarks = ["sp-runtime/runtime-benchmarks",]
31+
#
32+
#try-runtime = [
33+
# "pallet-transaction-payment/try-runtime",
34+
# "sp-runtime/try-runtime"
35+
#]
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
#![cfg_attr(not(feature = "std"), no_std)]
2+
use pallet_transaction_payment::{Multiplier, MultiplierUpdate, TargetedFeeAdjustment};
3+
use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
4+
use sp_runtime::{
5+
traits::{Bounded, Convert, Get},
6+
FixedPointNumber, Perquintill, SaturatedConversion, Saturating,
7+
};
8+
use sp_weights::Weight;
9+
10+
frame_support::parameter_types! {
11+
pub storage NextLengthMultiplier: Multiplier = Multiplier::saturating_from_integer(1);
12+
}
13+
14+
/// Currently, the `pallet_transaction_payment` uses the following formula:
15+
///
16+
/// ```ignore
17+
/// inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee];
18+
/// ```
19+
///
20+
/// This formula allows us to update `targeted_fee_adjustment` at the end of each block
21+
/// using `FeeMultiplierUpdate`. This associated type is called within the `on_finalize`
22+
/// function of the `transaction_payment` pallet, with the purpose of converting the existing
23+
/// `targeted_fee_adjustment` to a new one based on network congestion.
24+
///
25+
/// The goal of this struct is to achieve a modified fee calculation formula:
26+
///
27+
/// ```ignore
28+
/// inclusion_fee = base_fee + [targeted_length_fee_adjustment * length_fee] + [targeted_weight_fee_adjustment * weight_fee];
29+
/// ```
30+
///
31+
/// As you can see, `targeted_fee_adjustment` becomes `targeted_weight_fee_adjustment`,
32+
/// while the behavior remains the same. The side effect is adding the multiplier
33+
/// `targeted_length_fee_adjustment` to `length_fee`. This formula is achievable because the `transaction_payment`
34+
/// pallet uses the `compute_fee_raw` function, which computes the final fee associated with an
35+
/// extrinsic. This function utilizes the associated type `LengthToFee`, which converts the length
36+
/// of an extrinsic to a fee.
37+
///
38+
/// By default, the implementation of `LengthToFee` is a constant multiplication. However, we
39+
/// aim to achieve a dynamic formula that can adapt based on network usage. This requires a custom
40+
/// implementation of `LengthToFee` in addition to this struct, called `DynamicLengthToFee`.
41+
///
42+
/// This struct solely provides a dynamic update of `targeted_length_fee_adjustment` and
43+
/// `targeted_weight_fee_adjustment` based on block congestion and usage. The effective implementation
44+
/// of the formula described above is made possible by using `DynamicLengthToFee`.
45+
pub struct DynamicFeeAdjustment<
46+
T,
47+
TF, // TargetBlockFullness
48+
AF, //AdjustmentVariableBlockFullness
49+
MF, //MinimumMultiplierBlockFullness
50+
MaF, //MaximumMultiplierBlockFullness
51+
MBLS, //MaximumBlockLength
52+
TS, // TargetBlockSize
53+
AS, //AdjustmentVariableBlockSize,
54+
MS, //MinimumMultiplierBlockSize,
55+
MaS, //MaximumMultiplierBlockSize,
56+
>(core::marker::PhantomData<(T, TF, AF, MF, MaF, MBLS, TS, AS, MS, MaS)>);
57+
58+
impl<T, TF, AF, MF, MaF, MBLS, TS, AS, MS, MaS> Convert<Multiplier, Multiplier>
59+
for DynamicFeeAdjustment<T, TF, AF, MF, MaF, MBLS, TS, AS, MS, MaS>
60+
where
61+
T: frame_system::Config,
62+
TF: Get<Perquintill>,
63+
AF: Get<Multiplier>,
64+
MF: Get<Multiplier>,
65+
MaF: Get<Multiplier>,
66+
MBLS: Get<u32>,
67+
TS: Get<Perquintill>,
68+
AS: Get<Multiplier>,
69+
MS: Get<Multiplier>,
70+
MaS: Get<Multiplier>,
71+
{
72+
// This function should be a pure function used to update NextFeeMultiplier
73+
// but will also have the side effect of updating NextLengthMultiplier.
74+
fn convert(previous_fee_multiplier: Multiplier) -> Multiplier {
75+
// Update NextLengthMultiplier
76+
77+
// To update the value, the same formula as TargetedFeeAdjustment will be used.
78+
// The formula is described here:
79+
// https://research.web3.foundation/Polkadot/overview/token-economics#2-slow-adjusting-mechanism
80+
81+
// This is essentially a copy-paste of that function because it works with normalized measurements.
82+
// Therefore, the multipliers will be properly evaluated for ref_time, proof_size, and length of the extrinsic.
83+
84+
// The main problem is that TargetedFeeAdjustment::convert directly calls the storage to extract the weight
85+
// of the current block, so there is no way to pass the length as an input argument and reuse the function to
86+
// update also the length multiplier.
87+
// Therefore, all the necessary parts will be copied and pasted to properly update NextLengthMultiplier.
88+
89+
// Defensive only. The multiplier in storage should always be at most positive. Nonetheless
90+
// we recover here in case of errors, because any value below this would be stale and can
91+
// never change.
92+
let previous_len_multiplier = NextLengthMultiplier::get();
93+
let min_multiplier = MS::get();
94+
let max_multiplier = MaS::get();
95+
let previous_len_multiplier = previous_len_multiplier.max(min_multiplier);
96+
97+
// The limiting dimension is the length of all extrinsic
98+
let (normal_limiting_dimension, max_limiting_dimension) = (
99+
<frame_system::Pallet<T>>::all_extrinsics_len().min(MBLS::get()),
100+
MBLS::get() as u64,
101+
);
102+
103+
let target_block_size = TS::get();
104+
let adjustment_variable = AS::get();
105+
106+
let target_size = (target_block_size * max_limiting_dimension) as u128;
107+
let block_size = normal_limiting_dimension as u128;
108+
109+
// determines if the first_term is positive
110+
let positive = block_size >= target_size;
111+
let diff_abs = block_size.max(target_size) - block_size.min(target_size);
112+
113+
// defensive only, a test case assures that the maximum weight diff can fit in Multiplier
114+
// without any saturation.
115+
let diff = Multiplier::saturating_from_rational(diff_abs, max_limiting_dimension.max(1));
116+
let diff_squared = diff.saturating_mul(diff);
117+
118+
let v_squared_2 = adjustment_variable.saturating_mul(adjustment_variable)
119+
/ Multiplier::saturating_from_integer(2);
120+
121+
let first_term = adjustment_variable.saturating_mul(diff);
122+
let second_term = v_squared_2.saturating_mul(diff_squared);
123+
124+
let new_len_multiplier = if positive {
125+
let excess = first_term
126+
.saturating_add(second_term)
127+
.saturating_mul(previous_len_multiplier);
128+
previous_len_multiplier
129+
.saturating_add(excess)
130+
.clamp(min_multiplier, max_multiplier)
131+
} else {
132+
// Defensive-only: first_term > second_term. Safe subtraction.
133+
let negative = first_term
134+
.saturating_sub(second_term)
135+
.saturating_mul(previous_len_multiplier);
136+
previous_len_multiplier
137+
.saturating_sub(negative)
138+
.clamp(min_multiplier, max_multiplier)
139+
};
140+
141+
NextLengthMultiplier::set(&new_len_multiplier);
142+
143+
// Update NextFeeMultiplier
144+
//
145+
// Here is the tricky part: this method returns the new value associated with the old-fashioned `NextFeeMultiplier`,
146+
// because weight dynamic adjustment has been battle tested. Previously, we have updated the
147+
// `NextLengthMultiplier` used in `DynamicLengthToFee`.
148+
TargetedFeeAdjustment::<T, TF, AF, MF, MaF>::convert(previous_fee_multiplier)
149+
}
150+
}
151+
152+
impl<T, TF, AF, MF, MaF, MBLS, TS, AS, MS, MaS> MultiplierUpdate
153+
for DynamicFeeAdjustment<T, TF, AF, MF, MaF, MBLS, TS, AS, MS, MaS>
154+
where
155+
T: frame_system::Config,
156+
TF: Get<Perquintill>,
157+
AF: Get<Multiplier>,
158+
MF: Get<Multiplier>,
159+
MaF: Get<Multiplier>,
160+
MBLS: Get<u32>,
161+
TS: Get<Perquintill>,
162+
AS: Get<Multiplier>,
163+
MS: Get<Multiplier>,
164+
MaS: Get<Multiplier>,
165+
{
166+
fn min() -> Multiplier {
167+
MF::get()
168+
}
169+
fn max() -> Multiplier {
170+
MaF::get()
171+
}
172+
fn target() -> Perquintill {
173+
TF::get()
174+
}
175+
fn variability() -> Multiplier {
176+
AF::get()
177+
}
178+
}
179+
180+
pub struct DynamicLengthToFee<T, B, M>(core::marker::PhantomData<(T, B, M)>);
181+
182+
impl<T, B, M> sp_weights::WeightToFee for DynamicLengthToFee<T, B, M>
183+
where
184+
T: frame_system::Config,
185+
B: BaseArithmetic + From<u32> + Copy + Unsigned,
186+
M: Get<B>,
187+
{
188+
type Balance = B;
189+
190+
fn weight_to_fee(weight: &Weight) -> Self::Balance {
191+
// really weird but weight.ref_time will contain the length of the extrinsic
192+
let length_fee = Self::Balance::saturated_from(weight.ref_time()).saturating_mul(M::get());
193+
let multiplier = NextLengthMultiplier::get();
194+
195+
// final adjusted length fee
196+
multiplier.saturating_mul_int(length_fee)
197+
}
198+
}
199+
200+
/*
201+
#[cf//g(test)]
202+
mod //tests {
203+
//use super::*;
204+
//use crate::Runtime;
205+
//use sp_runtime::BuildStorage;
206+
207+
//fn new_test_ext() -> sp_io::TestExternalities {
208+
// frame_system::GenesisConfig::<Runtime>::default()
209+
// .build_storage()
210+
// .unwrap()
211+
// .into()
212+
//}
213+
214+
//#[test]
215+
//fn test_length_to_fee() {
216+
// // Test that inclusion fee is evaluated propertly
217+
// // following what done in BlobsLengthToFee
218+
// new_test_ext().execute_with(|| {
219+
// let len = 123;
220+
// let multiplier = Multiplier::saturating_from_integer(12);
221+
// NextLengthMultiplier::set(&multiplier);
222+
223+
// let length_fee = len * TransactionByteFee::get();
224+
// let expected = multiplier.saturating_mul_int(length_fee);
225+
226+
// assert_eq!(
227+
// pallet_transaction_payment::Pallet::<Runtime>::length_to_fee(len as u32),
228+
// expected
229+
// );
230+
// });
231+
//}
232+
}
233+
*/

0 commit comments

Comments
 (0)