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
261
pub fn round_issuance_range<T: Config>(round: Range<Perbill>) -> Range<BalanceOf<T>> {
103
261
	let circulating = T::Currency::total_issuance();
104
261
	Range {
105
261
		min: round.min * circulating,
106
261
		ideal: round.ideal * circulating,
107
261
		max: round.max * circulating,
108
261
	}
109
261
}
110

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

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

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