penumbra_stake/
penalty.rsuse ark_ff::ToConstraintField;
use decaf377::Fq;
use penumbra_proto::{penumbra::core::component::stake::v1 as pbs, DomainType};
use serde::{Deserialize, Serialize};
use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID};
use penumbra_num::{fixpoint::U128x128, Amount};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(try_from = "pbs::Penalty", into = "pbs::Penalty")]
pub struct Penalty(U128x128);
impl Penalty {
pub fn from_percent(percent: u64) -> Self {
Penalty::from_bps(percent.saturating_mul(100))
}
pub fn from_bps(bps: u64) -> Self {
Penalty::from_bps_squared(bps.saturating_mul(10000))
}
pub fn from_bps_squared(bps_squared: u64) -> Self {
assert!(bps_squared <= 1_0000_0000);
Self(U128x128::ratio(bps_squared, 1_0000_0000).expect(&format!(
"{bps_squared} bps^2 should be convertible to a U128x128"
)))
.one_minus_this()
}
fn one_minus_this(&self) -> Penalty {
Self(
(U128x128::from(1u64) - self.0)
.expect("1 - penalty should never underflow, because penalty is at most 1"),
)
}
pub fn kept_rate(&self) -> U128x128 {
self.0
}
pub fn compound(&self, other: Penalty) -> Penalty {
Self((self.0 * other.0).expect("compounding penalties will not overflow"))
}
pub fn apply_to_amount(&self, amount: Amount) -> Amount {
self.0
.apply_to_amount(&amount)
.expect("should not overflow, because penalty is <= 1")
}
pub fn apply_to(&self, amount: impl Into<U128x128>) -> U128x128 {
(amount.into() * self.0).expect("should not overflow, because penalty is <= 1")
}
pub fn balance_for_claim(&self, unbonding_id: asset::Id, unbonding_amount: Amount) -> Balance {
Balance::zero()
- Value {
amount: unbonding_amount,
asset_id: unbonding_id,
}
+ Value {
amount: self.apply_to_amount(unbonding_amount),
asset_id: *STAKING_TOKEN_ASSET_ID,
}
}
}
impl ToConstraintField<Fq> for Penalty {
fn to_field_elements(&self) -> Option<Vec<Fq>> {
self.0.to_field_elements()
}
}
impl From<Penalty> for [u8; 32] {
fn from(value: Penalty) -> Self {
value.0.into()
}
}
impl<'a> TryFrom<&'a [u8]> for Penalty {
type Error = <U128x128 as TryFrom<&'a [u8]>>::Error;
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
U128x128::try_from(value).map(Self)
}
}
impl DomainType for Penalty {
type Proto = pbs::Penalty;
}
impl From<Penalty> for pbs::Penalty {
fn from(v: Penalty) -> Self {
pbs::Penalty {
inner: <[u8; 32]>::from(v).to_vec(),
}
}
}
impl TryFrom<pbs::Penalty> for Penalty {
type Error = anyhow::Error;
fn try_from(v: pbs::Penalty) -> Result<Self, Self::Error> {
Ok(Penalty::try_from(v.inner.as_slice())?)
}
}