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
        collators_fraction >= 0.0 && collators_fraction <= 1.0,
41
        "collators_fraction must be between 0 and 1"
42
    );
43
3
    assert!(
44
3
        annual_inflation >= 0.0 && annual_inflation <= 1.0,
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 / (eras_per_year as f64)) - 1.0;
50
3

            
51
3
    // 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
3

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

            
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
1

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

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

            
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
1

            
79
1
    // "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
100

            
92
100
        let actual_era_inflation = supply as f64 / era_start_supply as f64 - 1.0;
93
100

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

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

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

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

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

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

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

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

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

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

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

            
165
#[test]
166
1
fn runtime_inflations_values_are_correct_in_fast() {
167
1
    runtime_inflations_values_are_correct_prod_or_fast(false)
168
1
}