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, DecodeWithMemTracking, 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
	DecodeWithMemTracking,
45
	Default,
46
	Deserialize,
47
	RuntimeDebug,
48
	MaxEncodedLen,
49
	Serialize,
50
120
	TypeInfo,
51
)]
52
pub struct Range<T> {
53
	pub min: T,
54
	pub ideal: T,
55
	pub max: T,
56
}
57

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

            
64
impl<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
75
26
pub fn perbill_annual_to_perbill_round(
76
26
	annual: Range<Perbill>,
77
26
	rounds_per_year: u32,
78
26
) -> Range<Perbill> {
79
26
	let exponent = I64F64::from_num(1) / I64F64::from_num(rounds_per_year);
80
78
	let annual_to_round = |annual: Perbill| -> Perbill {
81
78
		let x = I64F64::from_num(annual.deconstruct()) / I64F64::from_num(Perbill::ACCURACY);
82
78
		let y: I64F64 = floatpow(I64F64::from_num(1) + x, exponent)
83
78
			.expect("Cannot overflow since rounds_per_year is u32 so worst case 0; QED");
84
78
		Perbill::from_parts(
85
78
			((y - I64F64::from_num(1)) * I64F64::from_num(Perbill::ACCURACY))
86
78
				.ceil()
87
78
				.to_num::<u32>(),
88
78
		)
89
78
	};
90
26
	Range {
91
26
		min: annual_to_round(annual.min),
92
26
		ideal: annual_to_round(annual.ideal),
93
26
		max: annual_to_round(annual.max),
94
26
	}
95
26
}
96
/// Convert annual inflation rate range to round inflation range
97
3
pub fn annual_to_round<T: Config>(annual: Range<Perbill>) -> Range<Perbill> {
98
3
	let periods = rounds_per_year::<T>();
99
3
	perbill_annual_to_perbill_round(annual, periods)
100
3
}
101

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

            
116
#[derive(
117
60
	Eq, PartialEq, Clone, Encode, Decode, Default, Deserialize, RuntimeDebug, Serialize, TypeInfo,
118
)]
119
pub 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

            
128
impl<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
3
	pub fn set_round_from_annual<T: Config>(&mut self, new: Range<Perbill>) {
141
3
		self.round = annual_to_round::<T>(new);
142
3
	}
143
	/// Reset round inflation rate based on changes to round length
144
12
	pub fn reset_round<T: Config>(&mut self, new_length: u32) {
145
12
		let periods = (MS_PER_YEAR / T::BlockTime::get()) / (new_length as u64);
146
12
		self.round = perbill_annual_to_perbill_round(self.annual, periods as u32);
147
12
	}
148
	/// Set staking expectations
149
3
	pub fn set_expectations(&mut self, expect: Range<Balance>) {
150
3
		self.expect = expect;
151
3
	}
152
}
153

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