use penumbra_num::fixpoint::U128x128;
use penumbra_num::Amount;
use penumbra_proto::core::component::stake::v1::CurrentValidatorRateResponse;
use penumbra_proto::{penumbra::core::component::stake::v1 as pb, DomainType};
use penumbra_sct::epoch::Epoch;
use serde::{Deserialize, Serialize};
use crate::{validator::State, FundingStream, IdentityKey};
use crate::{Delegate, Penalty, Undelegate, BPS_SQUARED_SCALING_FACTOR};
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(try_from = "pb::RateData", into = "pb::RateData")]
pub struct RateData {
pub identity_key: IdentityKey,
pub validator_reward_rate: Amount,
pub validator_exchange_rate: Amount,
}
impl RateData {
pub fn next_epoch(
&self,
next_base_rate: &BaseRateData,
funding_streams: &[FundingStream],
validator_state: &State,
) -> RateData {
let previous_rate = self;
if let State::Active = validator_state {
let validator_commission_bps = funding_streams
.iter()
.fold(0u64, |total, stream| total + stream.rate_bps() as u64);
if validator_commission_bps > 1_0000 {
panic!("commission rate sums to > 100%")
}
let one = U128x128::from(1u128);
let max_bps = U128x128::from(1_0000u128);
let validator_commission_bps = U128x128::from(validator_commission_bps);
let next_base_reward_rate = U128x128::from(next_base_rate.base_reward_rate);
let previous_validator_exchange_rate =
U128x128::from(previous_rate.validator_exchange_rate);
let validator_commission =
(validator_commission_bps / max_bps).expect("max_bps is nonzero");
let next_base_reward_rate = (next_base_reward_rate / *BPS_SQUARED_SCALING_FACTOR)
.expect("scaling factor is nonzero");
let previous_validator_exchange_rate = (previous_validator_exchange_rate
/ *BPS_SQUARED_SCALING_FACTOR)
.expect("scaling factor is nonzero");
tracing::debug!(%validator_commission, %next_base_reward_rate, "computing validator reward rate");
let commission_factor =
(one - validator_commission).expect("0 <= validator_commission_bps <= 1");
tracing::debug!(%commission_factor, "complement commission rate");
let next_validator_reward_rate =
(next_base_reward_rate * commission_factor).expect("does not overflow");
tracing::debug!(%next_validator_reward_rate, "validator reward rate");
tracing::debug!(%next_validator_reward_rate, %previous_validator_exchange_rate, "computing validator exchange rate");
let reward_growth_factor =
(one + next_validator_reward_rate).expect("does not overflow");
let next_validator_exchange_rate = (previous_validator_exchange_rate
* reward_growth_factor)
.expect("does not overflow");
tracing::debug!(%next_validator_exchange_rate, "computed the validator exchange rate");
let next_validator_reward_rate = (next_validator_reward_rate
* *BPS_SQUARED_SCALING_FACTOR)
.expect("rate is between 0 and 1")
.round_down()
.try_into()
.expect("rounding down gives an integral type");
let next_validator_exchange_rate = (next_validator_exchange_rate
* *BPS_SQUARED_SCALING_FACTOR)
.expect("rate is between 0 and 1")
.round_down()
.try_into()
.expect("rounding down gives an integral type");
RateData {
identity_key: previous_rate.identity_key.clone(),
validator_reward_rate: next_validator_reward_rate,
validator_exchange_rate: next_validator_exchange_rate,
}
} else {
RateData {
identity_key: previous_rate.identity_key.clone(),
validator_reward_rate: previous_rate.validator_reward_rate,
validator_exchange_rate: previous_rate.validator_exchange_rate,
}
}
}
pub fn delegation_amount(&self, unbonded_amount: Amount) -> Amount {
let unbonded_amount = U128x128::from(unbonded_amount);
let validator_exchange_rate = U128x128::from(self.validator_exchange_rate);
let validator_exchange_rate = (validator_exchange_rate / *BPS_SQUARED_SCALING_FACTOR)
.expect("scaling factor is nonzero");
if validator_exchange_rate == U128x128::from(0u128) {
return 0u128.into();
}
let delegation_amount = (unbonded_amount / validator_exchange_rate)
.expect("validator exchange rate is nonzero");
delegation_amount
.round_down()
.try_into()
.expect("rounding down gives an integral type")
}
pub fn slash(&self, penalty: Penalty) -> Self {
let mut slashed = self.clone();
let penalized_exchange_rate: Amount = penalty
.apply_to(self.validator_exchange_rate)
.round_down()
.try_into()
.expect("multiplying will not overflow");
slashed.validator_exchange_rate = penalized_exchange_rate;
slashed
}
pub fn unbonded_amount(&self, delegation_amount: Amount) -> Amount {
let delegation_amount = U128x128::from(delegation_amount);
let validator_exchange_rate = U128x128::from(self.validator_exchange_rate);
let validator_exchange_rate = (validator_exchange_rate / *BPS_SQUARED_SCALING_FACTOR)
.expect("scaling factor is nonzero");
(delegation_amount * validator_exchange_rate)
.expect("does not overflow")
.round_down()
.try_into()
.expect("rounding down gives an integral type")
}
pub fn voting_power(&self, delegation_pool_size: Amount) -> Amount {
let delegation_pool_size = U128x128::from(delegation_pool_size);
let validator_exchange_rate = U128x128::from(self.validator_exchange_rate);
let validator_exchange_rate = (validator_exchange_rate / *BPS_SQUARED_SCALING_FACTOR)
.expect("scaling factor is nonzero");
let voting_power = (delegation_pool_size * validator_exchange_rate)
.expect("does not overflow")
.round_down()
.try_into()
.expect("rounding down gives an integral type");
voting_power
}
pub fn build_delegate(&self, epoch: Epoch, unbonded_amount: Amount) -> Delegate {
Delegate {
delegation_amount: self.delegation_amount(unbonded_amount),
epoch_index: epoch.index,
unbonded_amount,
validator_identity: self.identity_key.clone(),
}
}
pub fn build_undelegate(&self, start_epoch: Epoch, delegation_amount: Amount) -> Undelegate {
Undelegate {
from_epoch: start_epoch,
delegation_amount,
unbonded_amount: self.unbonded_amount(delegation_amount),
validator_identity: self.identity_key.clone(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(try_from = "pb::BaseRateData", into = "pb::BaseRateData")]
pub struct BaseRateData {
pub epoch_index: u64,
pub base_reward_rate: Amount,
pub base_exchange_rate: Amount,
}
impl BaseRateData {
pub fn next_epoch(&self, next_base_reward_rate: Amount) -> BaseRateData {
let prev_base_exchange_rate = U128x128::from(self.base_exchange_rate);
let next_base_reward_rate_scaled = next_base_reward_rate.clone();
let next_base_reward_rate = U128x128::from(next_base_reward_rate);
let one = U128x128::from(1u128);
let prev_base_exchange_rate = (prev_base_exchange_rate / *BPS_SQUARED_SCALING_FACTOR)
.expect("scaling factor is nonzero");
let next_base_reward_rate_fp = (next_base_reward_rate / *BPS_SQUARED_SCALING_FACTOR)
.expect("scaling factor is nonzero");
let reward_growth_factor = (one + next_base_reward_rate_fp).expect("does not overflow");
let next_base_exchange_rate =
(prev_base_exchange_rate * reward_growth_factor).expect("does not overflow");
let next_base_exchange_rate_scaled = (next_base_exchange_rate
* *BPS_SQUARED_SCALING_FACTOR)
.expect("rate is between 0 and 1")
.round_down()
.try_into()
.expect("rounding down gives an integral type");
BaseRateData {
base_exchange_rate: next_base_exchange_rate_scaled,
base_reward_rate: next_base_reward_rate_scaled,
epoch_index: self.epoch_index + 1,
}
}
}
impl DomainType for RateData {
type Proto = pb::RateData;
}
impl From<RateData> for pb::RateData {
#[allow(deprecated)]
fn from(v: RateData) -> Self {
pb::RateData {
identity_key: Some(v.identity_key.into()),
epoch_index: 0,
validator_reward_rate: Some(v.validator_reward_rate.into()),
validator_exchange_rate: Some(v.validator_exchange_rate.into()),
}
}
}
impl TryFrom<pb::RateData> for RateData {
type Error = anyhow::Error;
fn try_from(v: pb::RateData) -> Result<Self, Self::Error> {
Ok(RateData {
identity_key: v
.identity_key
.ok_or_else(|| anyhow::anyhow!("missing identity key"))?
.try_into()?,
validator_reward_rate: v
.validator_reward_rate
.ok_or_else(|| anyhow::anyhow!("empty validator reward rate in RateData message"))?
.try_into()?,
validator_exchange_rate: v
.validator_exchange_rate
.ok_or_else(|| {
anyhow::anyhow!("empty validator exchange rate in RateData message")
})?
.try_into()?,
})
}
}
impl DomainType for BaseRateData {
type Proto = pb::BaseRateData;
}
impl From<BaseRateData> for pb::BaseRateData {
fn from(rate: BaseRateData) -> Self {
pb::BaseRateData {
epoch_index: rate.epoch_index,
base_reward_rate: Some(rate.base_reward_rate.into()),
base_exchange_rate: Some(rate.base_exchange_rate.into()),
}
}
}
impl TryFrom<pb::BaseRateData> for BaseRateData {
type Error = anyhow::Error;
fn try_from(v: pb::BaseRateData) -> Result<Self, Self::Error> {
Ok(BaseRateData {
epoch_index: v.epoch_index,
base_reward_rate: v
.base_reward_rate
.ok_or_else(|| anyhow::anyhow!("empty base reward rate in BaseRateData message"))?
.try_into()?,
base_exchange_rate: v
.base_exchange_rate
.ok_or_else(|| anyhow::anyhow!("empty base exchange rate in BaseRateData message"))?
.try_into()?,
})
}
}
impl From<RateData> for CurrentValidatorRateResponse {
fn from(r: RateData) -> Self {
CurrentValidatorRateResponse {
data: Some(r.into()),
}
}
}
impl TryFrom<CurrentValidatorRateResponse> for RateData {
type Error = anyhow::Error;
fn try_from(value: CurrentValidatorRateResponse) -> Result<Self, Self::Error> {
value
.data
.ok_or_else(|| anyhow::anyhow!("empty CurrentValidatorRateResponse message"))?
.try_into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use decaf377_rdsa as rdsa;
use rand_core::OsRng;
#[test]
fn slash_rate_by_penalty() {
let vk = rdsa::VerificationKey::from(rdsa::SigningKey::new(OsRng));
let ik = IdentityKey(vk.into());
let rate_data = RateData {
identity_key: ik,
validator_reward_rate: 1_0000_0000u128.into(),
validator_exchange_rate: 2_0000_0000u128.into(),
};
let penalty = Penalty::from_percent(10);
let slashed = rate_data.slash(penalty);
assert_eq!(slashed.validator_exchange_rate, 1_8000_0000u128.into());
}
}