pallet_base_fee/
lib.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # BaseFee pallet
19//!
20//! The BaseFee pallet is responsible for managing the `BaseFeePerGas` value.
21//! This pallet can dynamically adjust the `BaseFeePerGas` by utilizing `Elasticity`.
22
23#![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		/// Lower and upper bounds for increasing / decreasing `BaseFeePerGas`.
54		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			// Register the Weight used on_finalize.
123			// 	- One storage read to get the block_weight.
124			// 	- One storage read to get the Elasticity.
125			// 	- One write to BaseFeePerGas.
126			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				// Zero elasticity means constant BaseFeePerGas.
133				return;
134			}
135
136			let lower = T::Threshold::lower();
137			let upper = T::Threshold::upper();
138			// `target` is the ideal congestion of the network where the base fee should remain unchanged.
139			// Under normal circumstances the `target` should be 50%.
140			// If we go below the `target`, the base fee is linearly decreased by the Elasticity delta of lower~target.
141			// If we go above the `target`, the base fee is linearly increased by the Elasticity delta of upper~target.
142			// The base fee is fully increased (default 12.5%) if the block is upper full (default 100%).
143			// The base fee is fully decreased (default 12.5%) if the block is lower empty (default 0%).
144			let weight = <frame_system::Pallet<T>>::block_weight();
145			let max_weight = <<T as frame_system::Config>::BlockWeights>::get().max_block;
146
147			// We convert `weight` into block fullness and ensure we are within the lower and upper bound.
148			let weight_used =
149				Permill::from_rational(weight.total().ref_time(), max_weight.ref_time())
150					.clamp(lower, upper);
151			// After clamp `weighted_used` is always between `lower` and `upper`.
152			// We scale the block fullness range to the lower/upper range, and the usage represents the
153			// actual percentage within this new scale.
154			let usage = (weight_used - lower) / (upper - lower);
155
156			// Target is our ideal block fullness.
157			let target = T::Threshold::ideal();
158			if usage > target {
159				// Above target, increase.
160				let coef = Permill::from_parts((usage.deconstruct() - target.deconstruct()) * 2u32);
161				// How much of the Elasticity is used to mutate base fee.
162				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						// Normalize to GWEI.
166						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				// Below target, decrease.
176				let coef = Permill::from_parts((target.deconstruct() - usage.deconstruct()) * 2u32);
177				// How much of the Elasticity is used to mutate base fee.
178				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						// Normalize to GWEI.
182						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						// lowest fee is norm(DefaultBaseFeePerGas * Threshold::ideal()):
187						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}