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
18
use crate::pallet::{BalanceOf, Config, Pallet};
19
use frame_support::traits::{Currency, Get};
20
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
21
use scale_info::TypeInfo;
22
use serde::{Deserialize, Serialize};
23
use sp_runtime::PerThing;
24
use sp_runtime::{Perbill, RuntimeDebug};
25
use substrate_fixed::transcendental::pow as floatpow;
26
use substrate_fixed::types::I64F64;
27

            
28
// Milliseconds per year
29
const MS_PER_YEAR: u64 = 31_557_600_000;
30

            
31
3
fn rounds_per_year<T: Config>() -> u32 {
32
3
	let blocks_per_round = <Pallet<T>>::round().length as u64;
33
3
	let blocks_per_year = MS_PER_YEAR / T::BlockTime::get();
34
3
	(blocks_per_year / blocks_per_round) as u32
35
3
}
36

            
37
#[derive(
38
	Eq,
39
	PartialEq,
40
	Clone,
41
	Copy,
42
	Encode,
43
	Decode,
44
	Default,
45
	Deserialize,
46
	RuntimeDebug,
47
	MaxEncodedLen,
48
	Serialize,
49
120
	TypeInfo,
50
)]
51
pub struct Range<T> {
52
	pub min: T,
53
	pub ideal: T,
54
	pub max: T,
55
}
56

            
57
impl<T: Ord> Range<T> {
58
10
	pub fn is_valid(&self) -> bool {
59
10
		self.max >= self.ideal && self.ideal >= self.min
60
10
	}
61
}
62

            
63
impl<T: Ord + Copy> From<T> for Range<T> {
64
	fn from(other: T) -> Range<T> {
65
		Range {
66
			min: other,
67
			ideal: other,
68
			max: other,
69
		}
70
	}
71
}
72
/// Convert an annual inflation to a round inflation
73
/// round = (1+annual)^(1/rounds_per_year) - 1
74
26
pub fn perbill_annual_to_perbill_round(
75
26
	annual: Range<Perbill>,
76
26
	rounds_per_year: u32,
77
26
) -> Range<Perbill> {
78
26
	let exponent = I64F64::from_num(1) / I64F64::from_num(rounds_per_year);
79
78
	let annual_to_round = |annual: Perbill| -> Perbill {
80
78
		let x = I64F64::from_num(annual.deconstruct()) / I64F64::from_num(Perbill::ACCURACY);
81
78
		let y: I64F64 = floatpow(I64F64::from_num(1) + x, exponent)
82
78
			.expect("Cannot overflow since rounds_per_year is u32 so worst case 0; QED");
83
78
		Perbill::from_parts(
84
78
			((y - I64F64::from_num(1)) * I64F64::from_num(Perbill::ACCURACY))
85
78
				.ceil()
86
78
				.to_num::<u32>(),
87
78
		)
88
78
	};
89
26
	Range {
90
26
		min: annual_to_round(annual.min),
91
26
		ideal: annual_to_round(annual.ideal),
92
26
		max: annual_to_round(annual.max),
93
26
	}
94
26
}
95
/// Convert annual inflation rate range to round inflation range
96
3
pub fn annual_to_round<T: Config>(annual: Range<Perbill>) -> Range<Perbill> {
97
3
	let periods = rounds_per_year::<T>();
98
3
	perbill_annual_to_perbill_round(annual, periods)
99
3
}
100

            
101
/// Compute round issuance range from round inflation range and current total issuance
102
263
pub fn round_issuance_range<T: Config>(round: Range<Perbill>) -> Range<BalanceOf<T>> {
103
263
	let circulating = if let Some(threshold) = T::LinearInflationThreshold::get() {
104
236
		core::cmp::min(T::Currency::total_issuance(), threshold)
105
	} else {
106
27
		T::Currency::total_issuance()
107
	};
108
263
	Range {
109
263
		min: round.min * circulating,
110
263
		ideal: round.ideal * circulating,
111
263
		max: round.max * circulating,
112
263
	}
113
263
}
114

            
115
#[derive(
116
60
	Eq, PartialEq, Clone, Encode, Decode, Default, Deserialize, RuntimeDebug, Serialize, TypeInfo,
117
)]
118
pub struct InflationInfo<Balance> {
119
	/// Staking expectations
120
	pub expect: Range<Balance>,
121
	/// Annual inflation range
122
	pub annual: Range<Perbill>,
123
	/// Round inflation range
124
	pub round: Range<Perbill>,
125
}
126

            
127
impl<Balance> InflationInfo<Balance> {
128
	pub fn new<T: Config>(
129
		annual: Range<Perbill>,
130
		expect: Range<Balance>,
131
	) -> InflationInfo<Balance> {
132
		InflationInfo {
133
			expect,
134
			annual,
135
			round: annual_to_round::<T>(annual),
136
		}
137
	}
138
	/// Set round inflation range according to input annual inflation range
139
3
	pub fn set_round_from_annual<T: Config>(&mut self, new: Range<Perbill>) {
140
3
		self.round = annual_to_round::<T>(new);
141
3
	}
142
	/// Reset round inflation rate based on changes to round length
143
12
	pub fn reset_round<T: Config>(&mut self, new_length: u32) {
144
12
		let periods = (MS_PER_YEAR / T::BlockTime::get()) / (new_length as u64);
145
12
		self.round = perbill_annual_to_perbill_round(self.annual, periods as u32);
146
12
	}
147
	/// Set staking expectations
148
3
	pub fn set_expectations(&mut self, expect: Range<Balance>) {
149
3
		self.expect = expect;
150
3
	}
151
}
152

            
153
#[cfg(test)]
154
mod tests {
155
	use super::*;
156
9
	fn mock_annual_to_round(annual: Range<Perbill>, rounds_per_year: u32) -> Range<Perbill> {
157
9
		perbill_annual_to_perbill_round(annual, rounds_per_year)
158
9
	}
159
9
	fn mock_round_issuance_range(
160
9
		// Total circulating before minting
161
9
		circulating: u128,
162
9
		// Round inflation range
163
9
		round: Range<Perbill>,
164
9
	) -> Range<u128> {
165
9
		Range {
166
9
			min: round.min * circulating,
167
9
			ideal: round.ideal * circulating,
168
9
			max: round.max * circulating,
169
9
		}
170
9
	}
171
	#[test]
172
1
	fn simple_issuance_conversion() {
173
1
		// 5% inflation for 10_000_0000 = 500,000 minted over the year
174
1
		// let's assume there are 10 periods in a year
175
1
		// => mint 500_000 over 10 periods => 50_000 minted per period
176
1
		let expected_round_issuance_range: Range<u128> = Range {
177
1
			min: 48_909,
178
1
			ideal: 48_909,
179
1
			max: 48_909,
180
1
		};
181
1
		let schedule = Range {
182
1
			min: Perbill::from_percent(5),
183
1
			ideal: Perbill::from_percent(5),
184
1
			max: Perbill::from_percent(5),
185
1
		};
186
1
		assert_eq!(
187
1
			expected_round_issuance_range,
188
1
			mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10))
189
1
		);
190
1
	}
191
	#[test]
192
1
	fn range_issuance_conversion() {
193
1
		// 3-5% inflation for 10_000_0000 = 300_000-500,000 minted over the year
194
1
		// let's assume there are 10 periods in a year
195
1
		// => mint 300_000-500_000 over 10 periods => 30_000-50_000 minted per period
196
1
		let expected_round_issuance_range: Range<u128> = Range {
197
1
			min: 29_603,
198
1
			ideal: 39298,
199
1
			max: 48_909,
200
1
		};
201
1
		let schedule = Range {
202
1
			min: Perbill::from_percent(3),
203
1
			ideal: Perbill::from_percent(4),
204
1
			max: Perbill::from_percent(5),
205
1
		};
206
1
		assert_eq!(
207
1
			expected_round_issuance_range,
208
1
			mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10))
209
1
		);
210
1
	}
211
	#[test]
212
1
	fn expected_parameterization() {
213
1
		let expected_round_schedule: Range<u128> = Range {
214
1
			min: 45,
215
1
			ideal: 56,
216
1
			max: 56,
217
1
		};
218
1
		let schedule = Range {
219
1
			min: Perbill::from_percent(4),
220
1
			ideal: Perbill::from_percent(5),
221
1
			max: Perbill::from_percent(5),
222
1
		};
223
1
		assert_eq!(
224
1
			expected_round_schedule,
225
1
			mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 8766))
226
1
		);
227
1
	}
228
	#[test]
229
1
	fn inflation_does_not_panic_at_round_number_limit() {
230
1
		let schedule = Range {
231
1
			min: Perbill::from_percent(100),
232
1
			ideal: Perbill::from_percent(100),
233
1
			max: Perbill::from_percent(100),
234
1
		};
235
1
		mock_round_issuance_range(u32::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
236
1
		mock_round_issuance_range(u64::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
237
1
		mock_round_issuance_range(u128::MAX.into(), mock_annual_to_round(schedule, u32::MAX));
238
1
		mock_round_issuance_range(u32::MAX.into(), mock_annual_to_round(schedule, 1));
239
1
		mock_round_issuance_range(u64::MAX.into(), mock_annual_to_round(schedule, 1));
240
1
		mock_round_issuance_range(u128::MAX.into(), mock_annual_to_round(schedule, 1));
241
1
	}
242
}