penumbra_sdk_stake/
penalty.rs1use ark_ff::ToConstraintField;
2use decaf377::Fq;
3use penumbra_sdk_proto::{penumbra::core::component::stake::v1 as pbs, DomainType};
4use serde::{Deserialize, Serialize};
5
6use penumbra_sdk_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID};
7use penumbra_sdk_num::{fixpoint::U128x128, Amount};
8
9#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
17#[serde(try_from = "pbs::Penalty", into = "pbs::Penalty")]
18pub struct Penalty(U128x128);
19
20impl Penalty {
21 pub fn from_percent(percent: u64) -> Self {
25 Penalty::from_bps(percent.saturating_mul(100))
26 }
27
28 pub fn from_bps(bps: u64) -> Self {
32 Penalty::from_bps_squared(bps.saturating_mul(10000))
33 }
34
35 pub fn from_bps_squared(bps_squared: u64) -> Self {
38 assert!(bps_squared <= 1_0000_0000);
39 Self(U128x128::ratio(bps_squared, 1_0000_0000).expect(&format!(
40 "{bps_squared} bps^2 should be convertible to a U128x128"
41 )))
42 .one_minus_this()
43 }
44
45 fn one_minus_this(&self) -> Penalty {
46 Self(
47 (U128x128::from(1u64) - self.0)
48 .expect("1 - penalty should never underflow, because penalty is at most 1"),
49 )
50 }
51
52 pub fn kept_rate(&self) -> U128x128 {
56 self.0
57 }
58
59 pub fn compound(&self, other: Penalty) -> Penalty {
61 Self((self.0 * other.0).expect("compounding penalties will not overflow"))
62 }
63
64 pub fn apply_to_amount(&self, amount: Amount) -> Amount {
66 self.0
67 .apply_to_amount(&amount)
68 .expect("should not overflow, because penalty is <= 1")
69 }
70
71 pub fn apply_to(&self, amount: impl Into<U128x128>) -> U128x128 {
73 (amount.into() * self.0).expect("should not overflow, because penalty is <= 1")
74 }
75
76 pub fn balance_for_claim(&self, unbonding_id: asset::Id, unbonding_amount: Amount) -> Balance {
84 Balance::zero()
87 - Value {
88 amount: unbonding_amount,
89 asset_id: unbonding_id,
90 }
91 + Value {
92 amount: self.apply_to_amount(unbonding_amount),
93 asset_id: *STAKING_TOKEN_ASSET_ID,
94 }
95 }
96}
97
98impl ToConstraintField<Fq> for Penalty {
99 fn to_field_elements(&self) -> Option<Vec<Fq>> {
100 self.0.to_field_elements()
101 }
102}
103
104impl From<Penalty> for [u8; 32] {
105 fn from(value: Penalty) -> Self {
106 value.0.into()
107 }
108}
109
110impl<'a> TryFrom<&'a [u8]> for Penalty {
111 type Error = <U128x128 as TryFrom<&'a [u8]>>::Error;
112
113 fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
114 U128x128::try_from(value).map(Self)
115 }
116}
117
118impl DomainType for Penalty {
119 type Proto = pbs::Penalty;
120}
121
122impl From<Penalty> for pbs::Penalty {
123 fn from(v: Penalty) -> Self {
124 pbs::Penalty {
125 inner: <[u8; 32]>::from(v).to_vec(),
126 }
127 }
128}
129
130impl TryFrom<pbs::Penalty> for Penalty {
131 type Error = anyhow::Error;
132 fn try_from(v: pbs::Penalty) -> Result<Self, Self::Error> {
133 Ok(Penalty::try_from(v.inner.as_slice())?)
134 }
135}