referrerpolicy=no-referrer-when-downgrade

pallet_staking_reward_fn/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19
20//! Useful function for inflation for nominated proof of stake.
21
22use sp_arithmetic::{
23	biguint::BigUint,
24	traits::{SaturatedConversion, Zero},
25	PerThing, Perquintill,
26};
27
28/// Compute yearly inflation using function
29///
30/// ```ignore
31/// I(x) = for x between 0 and x_ideal: x / x_ideal,
32/// for x between x_ideal and 1: 2^((x_ideal - x) / d)
33/// ```
34///
35/// where:
36/// * x is the stake rate, i.e. fraction of total issued tokens that actively staked behind
37///   validators.
38/// * d is the falloff or `decay_rate`
39/// * x_ideal: the ideal stake rate.
40///
41/// The result is meant to be scaled with minimum inflation and maximum inflation.
42///
43/// (as detailed
44/// [here](https://research.web3.foundation/Polkadot/overview/token-economics#inflation-model-with-parachains))
45///
46/// Arguments are:
47/// * `stake`: The fraction of total issued tokens that actively staked behind validators. Known as
48///   `x` in the literature. Must be between 0 and 1.
49/// * `ideal_stake`: The fraction of total issued tokens that should be actively staked behind
50///   validators. Known as `x_ideal` in the literature. Must be between 0 and 1.
51/// * `falloff`: Known as `decay_rate` in the literature. A co-efficient dictating the strength of
52///   the global incentivization to get the `ideal_stake`. A higher number results in less typical
53///   inflation at the cost of greater volatility for validators. Must be more than 0.01.
54pub fn compute_inflation<P: PerThing>(stake: P, ideal_stake: P, falloff: P) -> P {
55	if stake < ideal_stake {
56		// ideal_stake is more than 0 because it is strictly more than stake
57		return stake / ideal_stake
58	}
59
60	if falloff < P::from_percent(1.into()) {
61		log::error!("Invalid inflation computation: falloff less than 1% is not supported");
62		return PerThing::zero()
63	}
64
65	let accuracy = {
66		let mut a = BigUint::from(Into::<u128>::into(P::ACCURACY));
67		a.lstrip();
68		a
69	};
70
71	let mut falloff = BigUint::from(falloff.deconstruct().into());
72	falloff.lstrip();
73
74	let ln2 = {
75		/// `ln(2)` expressed in as perquintillionth.
76		const LN2: u64 = 0_693_147_180_559_945_309;
77		let ln2 = P::from_rational(LN2.into(), Perquintill::ACCURACY.into());
78		BigUint::from(ln2.deconstruct().into())
79	};
80
81	// falloff is stripped above.
82	let ln2_div_d = div_by_stripped(ln2.mul(&accuracy), &falloff);
83
84	let inpos_param = INPoSParam {
85		x_ideal: BigUint::from(ideal_stake.deconstruct().into()),
86		x: BigUint::from(stake.deconstruct().into()),
87		accuracy,
88		ln2_div_d,
89	};
90
91	let res = compute_taylor_serie_part(&inpos_param);
92
93	match u128::try_from(res.clone()) {
94		Ok(res) if res <= Into::<u128>::into(P::ACCURACY) => P::from_parts(res.saturated_into()),
95		// If result is beyond bounds there is nothing we can do
96		_ => {
97			log::error!("Invalid inflation computation: unexpected result {:?}", res);
98			P::zero()
99		},
100	}
101}
102
103/// Internal struct holding parameter info alongside other cached value.
104///
105/// All expressed in part from `accuracy`
106struct INPoSParam {
107	ln2_div_d: BigUint,
108	x_ideal: BigUint,
109	x: BigUint,
110	/// Must be stripped and have no leading zeros.
111	accuracy: BigUint,
112}
113
114/// Compute `2^((x_ideal - x) / d)` using taylor serie.
115///
116/// x must be strictly more than x_ideal.
117///
118/// result is expressed with accuracy `INPoSParam.accuracy`
119fn compute_taylor_serie_part(p: &INPoSParam) -> BigUint {
120	// The last computed taylor term.
121	let mut last_taylor_term = p.accuracy.clone();
122
123	// Whereas taylor sum is positive.
124	let mut taylor_sum_positive = true;
125
126	// The sum of all taylor term.
127	let mut taylor_sum = last_taylor_term.clone();
128
129	for k in 1..300 {
130		last_taylor_term = compute_taylor_term(k, &last_taylor_term, p);
131
132		if last_taylor_term.is_zero() {
133			break
134		}
135
136		let last_taylor_term_positive = k % 2 == 0;
137
138		if taylor_sum_positive == last_taylor_term_positive {
139			taylor_sum = taylor_sum.add(&last_taylor_term);
140		} else if taylor_sum >= last_taylor_term {
141			taylor_sum = taylor_sum
142				.sub(&last_taylor_term)
143				// NOTE: Should never happen as checked above
144				.unwrap_or_else(|e| e);
145		} else {
146			taylor_sum_positive = !taylor_sum_positive;
147			taylor_sum = last_taylor_term
148				.clone()
149				.sub(&taylor_sum)
150				// NOTE: Should never happen as checked above
151				.unwrap_or_else(|e| e);
152		}
153	}
154
155	if !taylor_sum_positive {
156		return BigUint::zero()
157	}
158
159	taylor_sum.lstrip();
160	taylor_sum
161}
162
163/// Return the absolute value of k-th taylor term of `2^((x_ideal - x))/d` i.e.
164/// `((x - x_ideal) * ln(2) / d)^k / k!`
165///
166/// x must be strictly more x_ideal.
167///
168/// We compute the term from the last term using this formula:
169///
170/// `((x - x_ideal) * ln(2) / d)^k / k! == previous_term * (x - x_ideal) * ln(2) / d / k`
171///
172/// `previous_taylor_term` and result are expressed with accuracy `INPoSParam.accuracy`
173fn compute_taylor_term(k: u32, previous_taylor_term: &BigUint, p: &INPoSParam) -> BigUint {
174	let x_minus_x_ideal =
175		p.x.clone()
176			.sub(&p.x_ideal)
177			// NOTE: Should never happen, as x must be more than x_ideal
178			.unwrap_or_else(|_| BigUint::zero());
179
180	let res = previous_taylor_term.clone().mul(&x_minus_x_ideal).mul(&p.ln2_div_d).div_unit(k);
181
182	// p.accuracy is stripped by definition.
183	let res = div_by_stripped(res, &p.accuracy);
184	let mut res = div_by_stripped(res, &p.accuracy);
185
186	res.lstrip();
187	res
188}
189
190/// Compute a div b.
191///
192/// requires `b` to be stripped and have no leading zeros.
193fn div_by_stripped(mut a: BigUint, b: &BigUint) -> BigUint {
194	a.lstrip();
195
196	if b.len() == 0 {
197		log::error!("Computation error: Invalid division");
198		return BigUint::zero()
199	}
200
201	if b.len() == 1 {
202		return a.div_unit(b.checked_get(0).unwrap_or(1))
203	}
204
205	if b.len() > a.len() {
206		return BigUint::zero()
207	}
208
209	if b.len() == a.len() {
210		// 100_000^2 is more than 2^32-1, thus `new_a` has more limbs than `b`.
211		let mut new_a = a.mul(&BigUint::from(100_000u64.pow(2)));
212		new_a.lstrip();
213
214		debug_assert!(new_a.len() > b.len());
215		return new_a
216			.div(b, false)
217			.map(|res| res.0)
218			.unwrap_or_else(BigUint::zero)
219			.div_unit(100_000)
220			.div_unit(100_000)
221	}
222
223	a.div(b, false).map(|res| res.0).unwrap_or_else(BigUint::zero)
224}