// Copyright 2017-2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
//! Proc macro to generate the reward curve functions and tests.
mod log;
use log::log2;
use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2, Span};
use proc_macro_crate::crate_name;
use quote::{quote, ToTokens};
use std::convert::TryInto;
use syn::parse::{Parse, ParseStream};
/// Accepts a number of expressions to create a instance of PiecewiseLinear which represents the
/// NPoS curve (as detailed
/// [here](http://research.web3.foundation/en/latest/polkadot/Token%20Economics/#inflation-model))
/// for those parameters. Parameters are:
/// - `min_inflation`: the minimal amount to be rewarded between validators, expressed as a fraction
/// of total issuance. Known as `I_0` in the literature.
/// Expressed in millionth, must be between 0 and 1_000_000.
///
/// - `max_inflation`: the maximum amount to be rewarded between validators, expressed as a fraction
/// of total issuance. This is attained only when `ideal_stake` is achieved.
/// Expressed in millionth, must be between min_inflation and 1_000_000.
///
/// - `ideal_stake`: the fraction of total issued tokens that should be actively staked behind
/// validators. Known as `x_ideal` in the literature.
/// Expressed in millionth, must be between 0_100_000 and 0_900_000.
///
/// - `falloff`: Known as `decay_rate` in the literature. A co-efficient dictating the strength of
/// the global incentivization to get the `ideal_stake`. A higher number results in less typical
/// inflation at the cost of greater volatility for validators.
/// Expressed in millionth, must be between 0 and 1_000_000.
///
/// - `max_piece_count`: The maximum number of pieces in the curve. A greater number uses more
/// resources but results in higher accuracy.
/// Must be between 2 and 1_000.
///
/// - `test_precision`: The maximum error allowed in the generated test.
/// Expressed in millionth, must be between 0 and 1_000_000.
///
/// # Example
///
/// ```
/// # fn main() {}
/// use sp_runtime::curve::PiecewiseLinear;
///
/// pallet_staking_reward_curve::build! {
/// const I_NPOS: PiecewiseLinear<'static> = curve!(
/// min_inflation: 0_025_000,
/// max_inflation: 0_100_000,
/// ideal_stake: 0_500_000,
/// falloff: 0_050_000,
/// max_piece_count: 40,
/// test_precision: 0_005_000,
/// );
/// }
/// ```
#[proc_macro]
pub fn build(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as INposInput);
let points = compute_points(&input);
let declaration = generate_piecewise_linear(points);
let test_module = generate_test_module(&input);
let imports = match crate_name("sp-runtime") {
Ok(sp_runtime) => {
let ident = syn::Ident::new(&sp_runtime, Span::call_site());
quote!( extern crate #ident as _sp_runtime; )
},
Err(e) => syn::Error::new(Span::call_site(), &e).to_compile_error(),
};
let const_name = input.ident;
let const_type = input.typ;
quote!(
const #const_name: #const_type = {
#imports
#declaration
};
#test_module
).into()
}
const MILLION: u32 = 1_000_000;
mod keyword {
syn::custom_keyword!(curve);
syn::custom_keyword!(min_inflation);
syn::custom_keyword!(max_inflation);
syn::custom_keyword!(ideal_stake);
syn::custom_keyword!(falloff);
syn::custom_keyword!(max_piece_count);
syn::custom_keyword!(test_precision);
}
struct INposInput {
ident: syn::Ident,
typ: syn::Type,
min_inflation: u32,
ideal_stake: u32,
max_inflation: u32,
falloff: u32,
max_piece_count: u32,
test_precision: u32,
}
struct Bounds {
min: u32,
min_strict: bool,
max: u32,
max_strict: bool,
}
impl Bounds {
fn check(&self, value: u32) -> bool {
let wrong = (self.min_strict && value <= self.min)
|| (!self.min_strict && value < self.min)
|| (self.max_strict && value >= self.max)
|| (!self.max_strict && value > self.max);
!wrong
}
}
impl core::fmt::Display for Bounds {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"{}{:07}; {:07}{}",
if self.min_strict { "]" } else { "[" },
self.min,
self.max,
if self.max_strict { "[" } else { "]" },
)
}
}
fn parse_field(input: ParseStream, bounds: Bounds)
-> syn::Result
{
::parse(&input)?;
::parse(&input)?;
let value_lit = syn::LitInt::parse(&input)?;
let value: u32 = value_lit.base10_parse()?;
if !bounds.check(value) {
return Err(syn::Error::new(value_lit.span(), format!(
"Invalid {}: {}, must be in {}", Token::default().to_token_stream(), value, bounds,
)));
}
Ok(value)
}
impl Parse for INposInput {
fn parse(input: ParseStream) -> syn::Result {
let args_input;
::parse(&input)?;
let ident = ::parse(&input)?;
::parse(&input)?;
let typ = ::parse(&input)?;
::parse(&input)?;
::parse(&input)?;
::parse(&input)?;
syn::parenthesized!(args_input in input);
::parse(&input)?;
if !input.is_empty() {
return Err(input.error("expected end of input stream, no token expected"));
}
let min_inflation = parse_field::(&args_input, Bounds {
min: 0,
min_strict: true,
max: 1_000_000,
max_strict: false,
})?;
::parse(&args_input)?;
let max_inflation = parse_field::(&args_input, Bounds {
min: min_inflation,
min_strict: true,
max: 1_000_000,
max_strict: false,
})?;
::parse(&args_input)?;
let ideal_stake = parse_field::(&args_input, Bounds {
min: 0_100_000,
min_strict: false,
max: 0_900_000,
max_strict: false,
})?;
::parse(&args_input)?;
let falloff = parse_field::(&args_input, Bounds {
min: 0_010_000,
min_strict: false,
max: 1_000_000,
max_strict: false,
})?;
::parse(&args_input)?;
let max_piece_count = parse_field::(&args_input, Bounds {
min: 2,
min_strict: false,
max: 1_000,
max_strict: false,
})?;
::parse(&args_input)?;
let test_precision = parse_field::(&args_input, Bounds {
min: 0,
min_strict: false,
max: 1_000_000,
max_strict: false,
})?;