pallet_parachain_staking/
inflation.rs

1// Copyright 2019-2025 PureStake Inc.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Helper methods for computing issuance based on inflation
18use crate::pallet::{BalanceOf, Config, Pallet};
19use frame_support::traits::{fungible::Inspect, Get};
20use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
21use scale_info::TypeInfo;
22use serde::{Deserialize, Serialize};
23use sp_runtime::PerThing;
24use sp_runtime::{Perbill, RuntimeDebug};
25use substrate_fixed::transcendental::pow as floatpow;
26use substrate_fixed::types::I64F64;
27
28// Milliseconds per year
29const MS_PER_YEAR: u64 = 31_557_600_000;
30
31fn rounds_per_year<T: Config>() -> u32 {
32	let blocks_per_round = <Pallet<T>>::round().length as u64;
33	let blocks_per_year = MS_PER_YEAR / T::BlockTime::get();
34	(blocks_per_year / blocks_per_round) as u32
35}
36
37#[derive(
38	Eq,
39	PartialEq,
40	Clone,
41	Copy,
42	Encode,
43	Decode,
44	DecodeWithMemTracking,
45	Default,
46	Deserialize,
47	RuntimeDebug,
48	MaxEncodedLen,
49	Serialize,
50	TypeInfo,
51)]
52pub struct Range<T> {
53	pub min: T,
54	pub ideal: T,
55	pub max: T,
56}
57
58impl<T: Ord> Range<T> {
59	pub fn is_valid(&self) -> bool {
60		self.max >= self.ideal && self.ideal >= self.min
61	}
62}
63
64impl<T: Ord + Copy> From<T> for Range<T> {
65	fn from(other: T) -> Range<T> {
66		Range {
67			min: other,
68			ideal: other,
69			max: other,
70		}
71	}
72}
73/// Convert an annual inflation to a round inflation
74/// round = (1+annual)^(1/rounds_per_year) - 1
75pub fn perbill_annual_to_perbill_round(
76	annual: Range<Perbill>,
77	rounds_per_year: u32,
78) -> Range<Perbill> {
79	let exponent = I64F64::from_num(1) / I64F64::from_num(rounds_per_year);
80	let annual_to_round = |annual: Perbill| -> Perbill {
81		let x = I64F64::from_num(annual.deconstruct()) / I64F64::from_num(Perbill::ACCURACY);
82		let y: I64F64 = floatpow(I64F64::from_num(1) + x, exponent)
83			.expect("Cannot overflow since rounds_per_year is u32 so worst case 0; QED");
84		Perbill::from_parts(
85			((y - I64F64::from_num(1)) * I64F64::from_num(Perbill::ACCURACY))
86				.ceil()
87				.to_num::<u32>(),
88		)
89	};
90	Range {
91		min: annual_to_round(annual.min),
92		ideal: annual_to_round(annual.ideal),
93		max: annual_to_round(annual.max),
94	}
95}
96/// Convert annual inflation rate range to round inflation range
97pub fn annual_to_round<T: Config>(annual: Range<Perbill>) -> Range<Perbill> {
98	let periods = rounds_per_year::<T>();
99	perbill_annual_to_perbill_round(annual, periods)
100}
101
102/// Compute round issuance range from round inflation range and current total issuance
103pub fn round_issuance_range<T: Config>(round: Range<Perbill>) -> Range<BalanceOf<T>> {
104	let circulating = if let Some(threshold) = T::LinearInflationThreshold::get() {
105		core::cmp::min(T::Currency::total_issuance(), threshold)
106	} else {
107		T::Currency::total_issuance()
108	};
109	Range {
110		min: round.min * circulating,
111		ideal: round.ideal * circulating,
112		max: round.max * circulating,
113	}
114}
115
116#[derive(
117	Eq, PartialEq, Clone, Encode, Decode, Default, Deserialize, RuntimeDebug, Serialize, TypeInfo,
118)]
119pub struct InflationInfo<Balance> {
120	/// Staking expectations
121	pub expect: Range<Balance>,
122	/// Annual inflation range
123	pub annual: Range<Perbill>,
124	/// Round inflation range
125	pub round: Range<Perbill>,
126}
127
128impl<Balance> InflationInfo<Balance> {
129	pub fn new<T: Config>(
130		annual: Range<Perbill>,
131		expect: Range<Balance>,
132	) -> InflationInfo<Balance> {
133		InflationInfo {
134			expect,
135			annual,
136			round: annual_to_round::<T>(annual),
137		}
138	}
139	/// Set round inflation range according to input annual inflation range
140	pub fn set_round_from_annual<T: Config>(&mut self, new: Range<Perbill>) {
141		self.round = annual_to_round::<T>(new);
142	}
143	/// Reset round inflation rate based on changes to round length
144	pub fn reset_round<T: Config>(&mut self, new_length: u32) {
145		let periods = (MS_PER_YEAR / T::BlockTime::get()) / (new_length as u64);
146		self.round = perbill_annual_to_perbill_round(self.annual, periods as u32);
147	}
148	/// Set staking expectations
149	pub fn set_expectations(&mut self, expect: Range<Balance>) {
150		self.expect = expect;
151	}
152}
153
154#[cfg(test)]
155mod tests {
156	use super::*;
157	fn mock_annual_to_round(annual: Range<Perbill>, rounds_per_year: u32) -> Range<Perbill> {
158		perbill_annual_to_perbill_round(annual, rounds_per_year)
159	}
160	fn mock_round_issuance_range(
161		// Total circulating before minting
162		circulating: u128,
163		// Round inflation range
164		round: Range<Perbill>,
165	) -> Range<u128> {
166		Range {
167			min: round.min * circulating,
168			ideal: round.ideal * circulating,
169			max: round.max * circulating,
170		}
171	}
172	#[test]
173	fn simple_issuance_conversion() {
174		// 5% inflation for 10_000_0000 = 500,000 minted over the year
175		// let's assume there are 10 periods in a year
176		// => mint 500_000 over 10 periods => 50_000 minted per period
177		let expected_round_issuance_range: Range<u128> = Range {
178			min: 48_909,
179			ideal: 48_909,
180			max: 48_909,
181		};
182		let schedule = Range {
183			min: Perbill::from_percent(5),
184			ideal: Perbill::from_percent(5),
185			max: Perbill::from_percent(5),
186		};
187		assert_eq!(
188			expected_round_issuance_range,
189			mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10))
190		);
191	}
192	#[test]
193	fn range_issuance_conversion() {
194		// 3-5% inflation for 10_000_0000 = 300_000-500,000 minted over the year
195		// let's assume there are 10 periods in a year
196		// => mint 300_000-500_000 over 10 periods => 30_000-50_000 minted per period
197		let expected_round_issuance_range: Range<u128> = Range {
198			min: 29_603,
199			ideal: 39298,
200			max: 48_909,
201		};
202		let schedule = Range {
203			min: Perbill::from_percent(3),
204			ideal: Perbill::from_percent(4),
205			max: Perbill::from_percent(5),
206		};
207		assert_eq!(
208			expected_round_issuance_range,
209			mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10))
210		);
211	}
212	#[test]
213	fn expected_parameterization() {
214		let expected_round_schedule: Range<u128> = Range {
215			min: 45,
216			ideal: 56,
217			max: 56,
218		};
219		let schedule = Range {
220			min: Perbill::from_percent(4),
221			ideal: Perbill::from_percent(5),
222			max: Perbill::from_percent(5),
223		};
224		assert_eq!(
225			expected_round_schedule,
226			mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 8766))
227		);
228	}
229	#[test]
230	fn inflation_does_not_panic_at_round_number_limit() {
231		let schedule = Range {
232			min: Perbill::from_percent(100),
233			ideal: Perbill::from_percent(100),
234			max: Perbill::from_percent(100),
235		};
236		mock_round_issuance_range(u32::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
237		mock_round_issuance_range(u64::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
238		mock_round_issuance_range(u128::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
239		mock_round_issuance_range(u32::MAX.into(), mock_annual_to_round(schedule, 1));
240		mock_round_issuance_range(u64::MAX.into(), mock_annual_to_round(schedule, 1));
241		mock_round_issuance_range(u128::MAX.into(), mock_annual_to_round(schedule, 1));
242	}
243}