1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi 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
// Tanssi 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 Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
use crate::{
18
    tests::common::ExtBuilder, Balances, CollatorsInflationRatePerBlock, EpochDurationInBlocks,
19
    Perbill, Runtime, SessionsPerEra, ValidatorsInflationRatePerEra, DAYS,
20
};
21

            
22
#[derive(Debug)]
23
struct InflationRates {
24
    era_inflation: f64,
25
    collators_block_inflation: f64,
26
    validators_era_inflation: f64,
27
}
28

            
29
/// Computes the following inflation rates:
30
/// - Collators inflation rate (per block)
31
/// - Validators inflation rate (per era)
32
///   Era inflation is split between collators and validators using collators_fraction
33
3
fn compute_inflation_rates(
34
3
    annual_inflation: f64,
35
3
    collators_fraction: f64,
36
3
    eras_per_year: u32,
37
3
    blocks_per_era: u32,
38
3
) -> InflationRates {
39
3
    assert!(
40
3
        (0.0..=1.0).contains(&collators_fraction),
41
        "collators_fraction must be between 0 and 1"
42
    );
43
3
    assert!(
44
3
        (0.0..=1.0).contains(&annual_inflation),
45
        "annual_inflation is a % and should be between 0 (0%) and 1 (100%)"
46
    );
47

            
48
    // Compute era inflation based on annual inflation
49
3
    let era_inflation = (1.0 + annual_inflation).powf(1.0 / f64::from(eras_per_year)) - 1.0;
50

            
51
    // Compute collators and validators era inflation
52
3
    let collators_era_inflation = (1.0 + era_inflation).powf(collators_fraction) - 1.0;
53
3
    let validators_era_inflation = (1.0 + era_inflation).powf(1.0 - collators_fraction) - 1.0;
54

            
55
    // Compute collator block inflation
56
3
    let collators_block_inflation =
57
3
        (1.0 + collators_era_inflation).powf(1.0 / f64::from(blocks_per_era)) - 1.0;
58

            
59
3
    InflationRates {
60
3
        era_inflation,
61
3
        collators_block_inflation,
62
3
        validators_era_inflation,
63
3
    }
64
3
}
65

            
66
#[test]
67
1
fn formula_is_sound() {
68
1
    let eras_per_year = 100;
69
1
    let blocks_per_era = 100;
70
1
    let annual_inflation = 0.1; // 10%
71

            
72
1
    let rates = compute_inflation_rates(annual_inflation, 0.6, eras_per_year, blocks_per_era);
73

            
74
1
    println!("Rates: {rates:?}");
75

            
76
1
    let col_inf = Perbill::from_float(rates.collators_block_inflation);
77
1
    let val_inf = Perbill::from_float(rates.validators_era_inflation);
78

            
79
    // "big" supply to reduce rounding errors
80
1
    let initial_supply = 100_000_000_000_000_000u128;
81
1
    let mut supply = initial_supply;
82

            
83
100
    for era in 0..eras_per_year {
84
100
        let era_start_supply = supply;
85

            
86
10000
        for _block in 0..blocks_per_era {
87
10000
            supply += col_inf * supply;
88
10000
        }
89

            
90
100
        supply += val_inf * supply;
91

            
92
        #[allow(clippy::cast_precision_loss)]
93
100
        let actual_era_inflation = supply as f64 / era_start_supply as f64 - 1.0;
94

            
95
100
        println!("Era {era}: Supply {supply}, Actual inf: {actual_era_inflation}");
96
100
        assert!((actual_era_inflation - rates.era_inflation).abs() < 0.00001);
97
    }
98

            
99
    #[allow(clippy::cast_precision_loss)]
100
1
    let actual_annual_inflation = supply as f64 / initial_supply as f64 - 1.0;
101
1
    println!("Initial supply: {initial_supply}");
102
1
    println!("Final supply:   {supply}");
103
1
    println!("Actual annual inflation: {actual_annual_inflation}");
104
1
    assert!((actual_annual_inflation - annual_inflation).abs() < 0.00001);
105
1
}
106

            
107
2
fn runtime_inflations_values_are_correct_prod_or_fast(prod: bool) {
108
2
    ExtBuilder::default().build().execute_with(|| {
109
2
        let sessions_per_era = SessionsPerEra::prod_if(prod);
110
2
        let blocks_per_session = EpochDurationInBlocks::prod_if(prod);
111
2
        let blocks_per_era = blocks_per_session * sessions_per_era;
112
2
        let eras_per_year = (365 * DAYS) / blocks_per_era;
113

            
114
2
        let annual_inflation = 0.1; // 10%
115
2
        let collators_fraction = 0.5; // 50% of era inflation goes to collators.
116

            
117
2
        let rates = compute_inflation_rates(
118
2
            annual_inflation,
119
2
            collators_fraction,
120
2
            eras_per_year,
121
2
            blocks_per_era,
122
        );
123
2
        println!("{rates:?}");
124

            
125
2
        let col_inf = Perbill::from_float(rates.collators_block_inflation);
126
2
        let val_inf = Perbill::from_float(rates.validators_era_inflation);
127

            
128
2
        assert_eq!(
129
2
            CollatorsInflationRatePerBlock::prod_if(prod),
130
            col_inf,
131
            "Collators inflation didn't match"
132
        );
133
2
        assert_eq!(
134
2
            ValidatorsInflationRatePerEra::prod_if(prod),
135
            val_inf,
136
            "Validators inflation didn't match"
137
        );
138

            
139
2
        assert!(
140
2
            CollatorsInflationRatePerBlock::prod_if(prod)
141
2
                < ValidatorsInflationRatePerEra::prod_if(prod),
142
            "block inflation should be less than era inflation, are they swapped?"
143
        );
144

            
145
        // CollatorsInflationRatePerBlock must be used in the pallet that rewards
146
        // container chains collators.
147
2
        assert_eq!(
148
2
            <Runtime as pallet_inflation_rewards::Config>::InflationRate::get(),
149
2
            CollatorsInflationRatePerBlock::get(),
150
        );
151

            
152
        // ValidatorsInflationRatePerEra must be used in the pallet that rewards
153
        // external validators. In this pallet the getter directly provides the inflated
154
        // amount.
155
2
        assert_eq!(
156
2
            <Runtime as pallet_external_validators_rewards::Config>::EraInflationProvider::get(),
157
2
            ValidatorsInflationRatePerEra::get() * Balances::total_issuance(),
158
        );
159
2
    })
160
2
}
161

            
162
#[test]
163
1
fn runtime_inflations_values_are_correct_in_prod() {
164
1
    runtime_inflations_values_are_correct_prod_or_fast(true)
165
1
}
166

            
167
#[test]
168
1
fn runtime_inflations_values_are_correct_in_fast() {
169
1
    runtime_inflations_values_are_correct_prod_or_fast(false)
170
1
}