#![doc(html_favicon_url = "https://dev.namada.net/master/favicon.png")]
#![doc(html_logo_url = "https://dev.namada.net/master/rustdoc-logo.png")]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(rustdoc::private_intra_doc_links)]
#![warn(
missing_docs,
rust_2018_idioms,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_lossless,
clippy::arithmetic_side_effects,
clippy::dbg_macro,
clippy::print_stdout,
clippy::print_stderr
)]
pub use namada_core::arith;
use namada_core::arith::checked;
pub use namada_core::dec::Dec;
pub use namada_core::uint::Uint;
use thiserror::Error;
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct PDController {
total_native_amount: Uint,
max_reward_rate: Dec,
last_inflation_amount: Uint,
p_gain_nom: Dec,
d_gain_nom: Dec,
epochs_per_year: u64,
target_metric: Dec,
last_metric: Dec,
}
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub enum Error {
#[error("Arithmetic {0}")]
Arith(#[from] arith::Error),
#[error("Decimal {0}")]
Dec(#[from] namada_core::dec::Error),
#[error("Max inflation overflow")]
MaxInflationOverflow,
#[error("Inflation amount overflow")]
InflationOverflow,
}
impl PDController {
#[allow(clippy::too_many_arguments)]
pub fn new(
total_native_amount: Uint,
max_reward_rate: Dec,
last_inflation_amount: Uint,
p_gain_nom: Dec,
d_gain_nom: Dec,
epochs_per_year: u64,
target_metric: Dec,
last_metric: Dec,
) -> PDController {
PDController {
total_native_amount,
max_reward_rate,
last_inflation_amount,
p_gain_nom,
d_gain_nom,
epochs_per_year,
target_metric,
last_metric,
}
}
pub fn compute_inflation(
&self,
control_coeff: Dec,
current_metric: Dec,
) -> Result<Uint, Error> {
let control = self.compute_control(control_coeff, current_metric)?;
self.compute_inflation_aux(control)
}
pub fn get_total_native_dec(&self) -> Result<Dec, Error> {
Dec::try_from(self.total_native_amount).map_err(Into::into)
}
pub fn get_epochs_per_year(&self) -> u64 {
self.epochs_per_year
}
fn get_max_inflation(&self) -> Result<Uint, Error> {
let total_native = self.get_total_native_dec()?;
let epochs_py: Dec = self.epochs_per_year.into();
let max_inflation =
checked!(total_native * self.max_reward_rate / epochs_py)?;
max_inflation.to_uint().ok_or(Error::MaxInflationOverflow)
}
fn compute_inflation_aux(&self, control: Dec) -> Result<Uint, Error> {
let last_inflation_amount = Dec::try_from(self.last_inflation_amount)?;
let new_inflation_amount = checked!(last_inflation_amount + control)?;
let new_inflation_amount = if new_inflation_amount.is_negative() {
Uint::zero()
} else {
new_inflation_amount
.to_uint()
.ok_or(Error::InflationOverflow)?
};
let max_inflation = self.get_max_inflation()?;
Ok(std::cmp::min(new_inflation_amount, max_inflation))
}
fn compute_control(
&self,
coeff: Dec,
current_metric: Dec,
) -> Result<Dec, arith::Error> {
let val: Dec = checked!(
current_metric * (self.d_gain_nom - self.p_gain_nom)
+ (self.target_metric * self.p_gain_nom)
- (self.last_metric * self.d_gain_nom)
)?;
checked!(coeff * val)
}
}