|
| 1 | +#![cfg_attr(not(feature = "std"), no_std)] |
| 2 | + |
| 3 | +pub use pallet::*; |
| 4 | + |
| 5 | +/// Currently, the `pallet_transaction_payment` uses the following formula: |
| 6 | +/// |
| 7 | +/// ```ignore |
| 8 | +/// inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee]; |
| 9 | +/// ``` |
| 10 | +/// |
| 11 | +/// This formula allows us to update `targeted_fee_adjustment` at the end of each block |
| 12 | +/// using `FeeMultiplierUpdate`, this associated type is called within the `on_finalize` |
| 13 | +/// function of the `transaction_payment` pallet, with the purpose of converting the existing |
| 14 | +/// `targeted_fee_adjustment` to a new one based on network congestion. |
| 15 | +/// |
| 16 | +/// The goal of this pallet is to achieve a modified fee calculation formula: |
| 17 | +/// |
| 18 | +/// ```ignore |
| 19 | +/// inclusion_fee = base_fee + [targeted_length_fee_adjustment * length_fee] + [targeted_weight_fee_adjustment * weight_fee]; |
| 20 | +/// ``` |
| 21 | +/// |
| 22 | +/// `targeted_fee_adjustment` becomes `targeted_weight_fee_adjustment`, |
| 23 | +/// while the behavior remains the same. |
| 24 | +/// `targeted_length_fee_adjustment` is a new multiplier associate to `length_fee`. |
| 25 | +/// This formula is achievable because the `transaction_payment` |
| 26 | +/// pallet uses the `compute_fee_raw` function, which computes the final fee associated with an |
| 27 | +/// extrinsic. This function utilizes the associated type `LengthToFee`, which converts the length |
| 28 | +/// of an extrinsic to a fee. |
| 29 | +/// |
| 30 | +/// By default, the implementation of `LengthToFee` is a constant multiplication. However, we |
| 31 | +/// aim to achieve a dynamic formula, thanks to the new multiplier stored in `NextLenghtMultiplier`, implementing `sp_weights::WeightToFee` |
| 32 | +/// for the Pallet struct and thus being able to use it as value for the associated type `LengthToFee` in the |
| 33 | +/// `pallet_transaction_type::Config`. |
| 34 | +/// |
| 35 | +/// `targeted_length_fee_adjustment` is updated at the end of each block inside `on_finalize` |
| 36 | +#[frame_support::pallet] |
| 37 | +pub mod pallet { |
| 38 | + |
| 39 | + use frame_support::pallet_prelude::*; |
| 40 | + use frame_system::pallet_prelude::*; |
| 41 | + use pallet_transaction_payment::{Multiplier, OnChargeTransaction}; |
| 42 | + use sp_runtime::{traits::Get, FixedPointNumber, Perquintill, SaturatedConversion, Saturating}; |
| 43 | + |
| 44 | + /// Configure the pallet by specifying the parameters and types on which it depends. |
| 45 | + #[pallet::config] |
| 46 | + pub trait Config: frame_system::Config + pallet_transaction_payment::Config { |
| 47 | + // `targeted_length_fee_adjustment` parameters |
| 48 | + #[pallet::constant] |
| 49 | + type TransactionByteFee: Get<<<Self as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<Self>>::Balance>; |
| 50 | + #[pallet::constant] |
| 51 | + type MaximumBlockLength: Get<u32>; |
| 52 | + #[pallet::constant] |
| 53 | + type AdjustmentVariableBlockSize: Get<Multiplier>; |
| 54 | + #[pallet::constant] |
| 55 | + type MinimumMultiplierBlockSize: Get<Multiplier>; |
| 56 | + #[pallet::constant] |
| 57 | + type MaximumMultiplierBlockSize: Get<Multiplier>; |
| 58 | + } |
| 59 | + |
| 60 | + #[pallet::pallet] |
| 61 | + pub struct Pallet<T>(_); |
| 62 | + |
| 63 | + pub struct NextLengthMultiplierDefualt; |
| 64 | + impl Get<Multiplier> for NextLengthMultiplierDefualt { |
| 65 | + fn get() -> Multiplier { |
| 66 | + Multiplier::saturating_from_integer(1) |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + #[pallet::storage] |
| 71 | + pub type NextLengthMultiplier<T: Config> = |
| 72 | + StorageValue<_, Multiplier, ValueQuery, NextLengthMultiplierDefualt>; |
| 73 | + |
| 74 | + pub struct TargetBlockSizeDefault; |
| 75 | + impl Get<Perquintill> for TargetBlockSizeDefault { |
| 76 | + fn get() -> Perquintill { |
| 77 | + Perquintill::from_percent(16) // 0.8MiB |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + #[pallet::storage] |
| 82 | + pub type TargetBlockSize<T: Config> = |
| 83 | + StorageValue<_, Perquintill, ValueQuery, TargetBlockSizeDefault>; |
| 84 | + |
| 85 | + #[pallet::hooks] |
| 86 | + impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { |
| 87 | + fn on_initialize(_: BlockNumberFor<T>) -> Weight { |
| 88 | + // TODO: implement skip block logic |
| 89 | + // https://github.com/thrumdev/blobs/issues/165 |
| 90 | + |
| 91 | + // NextLengthMultiplier: 1r + 1w |
| 92 | + // TargetBlockSize: 1r |
| 93 | + T::DbWeight::get().reads_writes(2, 1) |
| 94 | + } |
| 95 | + |
| 96 | + fn on_finalize(_n: BlockNumberFor<T>) { |
| 97 | + // update targeted_weight_fee_adjustment, |
| 98 | + // contained in NextLengthMultiplier storage item |
| 99 | + |
| 100 | + // This is essentially a copy-paste of the function TargetedFeeAdjustment::convert. |
| 101 | + // The main problem is that TargetedFeeAdjustment::convert directly calls the storage to extract the weight |
| 102 | + // of the current block, so there is no way to pass the length as an input argument and reuse the function to |
| 103 | + // update also the length multiplier. |
| 104 | + // Therefore, all the necessary parts taken and properly adapted to update NextLengthMultiplier. |
| 105 | + |
| 106 | + // Defensive only. The multiplier in storage should always be at most positive. Nonetheless |
| 107 | + // we recover here in case of errors, because any value below this would be stale and can |
| 108 | + // never change. |
| 109 | + let previous_len_multiplier = NextLengthMultiplier::<T>::get(); |
| 110 | + let min_multiplier = T::MinimumMultiplierBlockSize::get(); |
| 111 | + let max_multiplier = T::MaximumMultiplierBlockSize::get(); |
| 112 | + let previous_len_multiplier = previous_len_multiplier.max(min_multiplier); |
| 113 | + |
| 114 | + // The limiting dimension is the length of all extrinsic |
| 115 | + let (normal_limiting_dimension, max_limiting_dimension) = ( |
| 116 | + <frame_system::Pallet<T>>::all_extrinsics_len().min(T::MaximumBlockLength::get()), |
| 117 | + T::MaximumBlockLength::get() as u64, |
| 118 | + ); |
| 119 | + |
| 120 | + let target_block_size = TargetBlockSize::<T>::get(); |
| 121 | + let adjustment_variable = T::AdjustmentVariableBlockSize::get(); |
| 122 | + |
| 123 | + let target_size = (target_block_size * max_limiting_dimension) as u128; |
| 124 | + let block_size = normal_limiting_dimension as u128; |
| 125 | + |
| 126 | + // determines if the first_term is positive |
| 127 | + let positive = block_size >= target_size; |
| 128 | + let diff_abs = block_size.max(target_size) - block_size.min(target_size); |
| 129 | + |
| 130 | + // defensive only, a test case assures that the maximum weight diff can fit in Multiplier |
| 131 | + // without any saturation. |
| 132 | + let diff = |
| 133 | + Multiplier::saturating_from_rational(diff_abs, max_limiting_dimension.max(1)); |
| 134 | + let diff_squared = diff.saturating_mul(diff); |
| 135 | + |
| 136 | + let v_squared_2 = adjustment_variable.saturating_mul(adjustment_variable) |
| 137 | + / Multiplier::saturating_from_integer(2); |
| 138 | + |
| 139 | + let first_term = adjustment_variable.saturating_mul(diff); |
| 140 | + let second_term = v_squared_2.saturating_mul(diff_squared); |
| 141 | + |
| 142 | + let new_len_multiplier = if positive { |
| 143 | + let excess = first_term |
| 144 | + .saturating_add(second_term) |
| 145 | + .saturating_mul(previous_len_multiplier); |
| 146 | + previous_len_multiplier |
| 147 | + .saturating_add(excess) |
| 148 | + .clamp(min_multiplier, max_multiplier) |
| 149 | + } else { |
| 150 | + // Defensive-only: first_term > second_term. Safe subtraction. |
| 151 | + let negative = first_term |
| 152 | + .saturating_sub(second_term) |
| 153 | + .saturating_mul(previous_len_multiplier); |
| 154 | + previous_len_multiplier |
| 155 | + .saturating_sub(negative) |
| 156 | + .clamp(min_multiplier, max_multiplier) |
| 157 | + }; |
| 158 | + |
| 159 | + NextLengthMultiplier::<T>::put(new_len_multiplier); |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + impl<T: Config + pallet_transaction_payment::Config> sp_weights::WeightToFee for Pallet<T> { |
| 164 | + type Balance = <<T as pallet_transaction_payment::Config>::OnChargeTransaction as OnChargeTransaction<T>>::Balance; |
| 165 | + |
| 166 | + fn weight_to_fee(weight: &Weight) -> Self::Balance { |
| 167 | + // really weird but weight.ref_time will contain the length of the extrinsic |
| 168 | + let length_fee = Self::Balance::saturated_from(weight.ref_time()) |
| 169 | + .saturating_mul(T::TransactionByteFee::get()); |
| 170 | + let multiplier = NextLengthMultiplier::<T>::get(); |
| 171 | + |
| 172 | + // final adjusted length fee |
| 173 | + multiplier.saturating_mul_int(length_fee) |
| 174 | + } |
| 175 | + } |
| 176 | +} |
0 commit comments