1#![cfg_attr(not(feature = "std"), no_std)]
24#![allow(clippy::comparison_chain)]
25#![warn(unused_crate_dependencies)]
26
27#[cfg(test)]
28mod tests;
29
30use frame_support::{traits::Get, weights::Weight};
31use sp_core::U256;
32use sp_runtime::Permill;
33
34pub trait BaseFeeThreshold {
35 fn lower() -> Permill;
36 fn ideal() -> Permill;
37 fn upper() -> Permill;
38}
39
40pub use self::pallet::*;
41
42#[frame_support::pallet]
43pub mod pallet {
44 use super::*;
45 use frame_support::pallet_prelude::*;
46 use frame_system::pallet_prelude::*;
47
48 #[pallet::pallet]
49 pub struct Pallet<T>(PhantomData<T>);
50
51 #[pallet::config]
52 pub trait Config: frame_system::Config {
53 type Threshold: BaseFeeThreshold;
55 type DefaultBaseFeePerGas: Get<U256>;
56 type DefaultElasticity: Get<Permill>;
57 }
58
59 #[pallet::genesis_config]
60 pub struct GenesisConfig<T: Config> {
61 pub base_fee_per_gas: U256,
62 pub elasticity: Permill,
63 #[serde(skip)]
64 pub _marker: PhantomData<T>,
65 }
66
67 impl<T: Config> GenesisConfig<T> {
68 pub fn new(base_fee_per_gas: U256, elasticity: Permill) -> Self {
69 Self {
70 base_fee_per_gas,
71 elasticity,
72 _marker: PhantomData,
73 }
74 }
75 }
76
77 impl<T: Config> Default for GenesisConfig<T> {
78 fn default() -> Self {
79 Self {
80 base_fee_per_gas: T::DefaultBaseFeePerGas::get(),
81 elasticity: T::DefaultElasticity::get(),
82 _marker: PhantomData,
83 }
84 }
85 }
86
87 #[pallet::genesis_build]
88 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
89 fn build(&self) {
90 <BaseFeePerGas<T>>::put(self.base_fee_per_gas);
91 <Elasticity<T>>::put(self.elasticity);
92 }
93 }
94
95 #[pallet::type_value]
96 pub fn DefaultBaseFeePerGas<T: Config>() -> U256 {
97 T::DefaultBaseFeePerGas::get()
98 }
99
100 #[pallet::storage]
101 pub type BaseFeePerGas<T> = StorageValue<_, U256, ValueQuery, DefaultBaseFeePerGas<T>>;
102
103 #[pallet::type_value]
104 pub fn DefaultElasticity<T: Config>() -> Permill {
105 T::DefaultElasticity::get()
106 }
107
108 #[pallet::storage]
109 pub type Elasticity<T> = StorageValue<_, Permill, ValueQuery, DefaultElasticity<T>>;
110
111 #[pallet::event]
112 #[pallet::generate_deposit(pub(super) fn deposit_event)]
113 pub enum Event {
114 NewBaseFeePerGas { fee: U256 },
115 BaseFeeOverflow,
116 NewElasticity { elasticity: Permill },
117 }
118
119 #[pallet::hooks]
120 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
121 fn on_initialize(_: BlockNumberFor<T>) -> Weight {
122 let db_weight = <T as frame_system::Config>::DbWeight::get();
127 db_weight.reads_writes(2, 1)
128 }
129
130 fn on_finalize(_n: BlockNumberFor<T>) {
131 if <Elasticity<T>>::get().is_zero() {
132 return;
134 }
135
136 let lower = T::Threshold::lower();
137 let upper = T::Threshold::upper();
138 let weight = <frame_system::Pallet<T>>::block_weight();
145 let max_weight = <<T as frame_system::Config>::BlockWeights>::get().max_block;
146
147 let weight_used =
149 Permill::from_rational(weight.total().ref_time(), max_weight.ref_time())
150 .clamp(lower, upper);
151 let usage = (weight_used - lower) / (upper - lower);
155
156 let target = T::Threshold::ideal();
158 if usage > target {
159 let coef = Permill::from_parts((usage.deconstruct() - target.deconstruct()) * 2u32);
161 let coef = <Elasticity<T>>::get() * coef;
163 <BaseFeePerGas<T>>::mutate(|bf| {
164 if let Some(scaled_basefee) = bf.checked_mul(U256::from(coef.deconstruct())) {
165 let increase = scaled_basefee
167 .checked_div(U256::from(1_000_000))
168 .unwrap_or_else(U256::zero);
169 *bf = bf.saturating_add(increase);
170 } else {
171 Self::deposit_event(Event::BaseFeeOverflow);
172 }
173 });
174 } else if usage < target {
175 let coef = Permill::from_parts((target.deconstruct() - usage.deconstruct()) * 2u32);
177 let coef = <Elasticity<T>>::get() * coef;
179 <BaseFeePerGas<T>>::mutate(|bf| {
180 if let Some(scaled_basefee) = bf.checked_mul(U256::from(coef.deconstruct())) {
181 let decrease = scaled_basefee
183 .checked_div(U256::from(1_000_000))
184 .unwrap_or_else(U256::zero);
185 let default_base_fee = T::DefaultBaseFeePerGas::get();
186 let lowest_base_fee = default_base_fee
188 .checked_mul(U256::from(T::Threshold::ideal().deconstruct()))
189 .unwrap_or(default_base_fee)
190 .checked_div(U256::from(1_000_000))
191 .unwrap_or(default_base_fee);
192 if bf.saturating_sub(decrease) >= lowest_base_fee {
193 *bf = bf.saturating_sub(decrease);
194 } else {
195 *bf = lowest_base_fee;
196 }
197 } else {
198 Self::deposit_event(Event::BaseFeeOverflow);
199 }
200 });
201 }
202 }
203 }
204
205 #[pallet::call]
206 impl<T: Config> Pallet<T> {
207 #[pallet::call_index(0)]
208 #[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
209 pub fn set_base_fee_per_gas(origin: OriginFor<T>, fee: U256) -> DispatchResult {
210 ensure_root(origin)?;
211 let _ = Self::set_base_fee_per_gas_inner(fee);
212 Self::deposit_event(Event::NewBaseFeePerGas { fee });
213 Ok(())
214 }
215
216 #[pallet::call_index(1)]
217 #[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())]
218 pub fn set_elasticity(origin: OriginFor<T>, elasticity: Permill) -> DispatchResult {
219 ensure_root(origin)?;
220 let _ = Self::set_elasticity_inner(elasticity);
221 Self::deposit_event(Event::NewElasticity { elasticity });
222 Ok(())
223 }
224 }
225}
226
227impl<T: Config> fp_evm::FeeCalculator for Pallet<T> {
228 fn min_gas_price() -> (U256, Weight) {
229 (<BaseFeePerGas<T>>::get(), T::DbWeight::get().reads(1))
230 }
231}
232
233impl<T: Config> Pallet<T> {
234 pub fn set_base_fee_per_gas_inner(value: U256) -> Weight {
235 <BaseFeePerGas<T>>::put(value);
236 T::DbWeight::get().writes(1)
237 }
238 pub fn set_elasticity_inner(value: Permill) -> Weight {
239 <Elasticity<T>>::put(value);
240 T::DbWeight::get().writes(1)
241 }
242}