penumbra_sdk_stake/undelegate_claim/
plan.rs1use decaf377::{Fq, Fr};
2use penumbra_sdk_asset::{asset, balance, Balance};
3use penumbra_sdk_num::Amount;
4use penumbra_sdk_proof_params::CONVERT_PROOF_PROVING_KEY;
5use penumbra_sdk_proto::{penumbra::core::component::stake::v1 as pb, DomainType};
6
7use serde::{Deserialize, Serialize};
8
9use crate::{
10 IdentityKey, Penalty, UnbondingToken, UndelegateClaim, UndelegateClaimBody,
11 UndelegateClaimProof,
12};
13
14use super::UndelegateClaimProofPublic;
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
17#[serde(try_from = "pb::UndelegateClaimPlan", into = "pb::UndelegateClaimPlan")]
18pub struct UndelegateClaimPlan {
19 pub validator_identity: IdentityKey,
21 pub penalty: Penalty,
23 pub unbonding_amount: Amount,
25 pub balance_blinding: Fr,
28 pub proof_blinding_r: Fq,
30 pub proof_blinding_s: Fq,
32 pub unbonding_start_height: u64,
34}
35
36impl UndelegateClaimPlan {
37 pub fn undelegate_claim(&self) -> UndelegateClaim {
39 UndelegateClaim {
40 body: self.undelegate_claim_body(),
41 proof: self.undelegate_claim_proof(),
42 }
43 }
44
45 pub fn undelegate_claim_body(&self) -> UndelegateClaimBody {
47 UndelegateClaimBody {
48 validator_identity: self.validator_identity,
49 unbonding_start_height: self.unbonding_start_height,
50 penalty: self.penalty,
51 balance_commitment: self.balance().commit(self.balance_blinding),
52 }
53 }
54
55 pub fn undelegate_claim_proof(&self) -> UndelegateClaimProof {
57 UndelegateClaimProof::prove(
58 self.proof_blinding_r,
59 self.proof_blinding_s,
60 &CONVERT_PROOF_PROVING_KEY,
61 UndelegateClaimProofPublic {
62 balance_commitment: self.balance_commitment(),
63 unbonding_id: self.unbonding_id(),
64 penalty: self.penalty,
65 },
66 super::UndelegateClaimProofPrivate {
67 unbonding_amount: self.unbonding_amount,
68 balance_blinding: self.balance_blinding,
69 },
70 )
71 .expect("can generate undelegate claim proof")
72 }
73
74 pub fn unbonding_token(&self) -> UnbondingToken {
75 UnbondingToken::new(self.validator_identity, self.unbonding_start_height)
76 }
77
78 pub fn unbonding_id(&self) -> asset::Id {
79 self.unbonding_token().id()
80 }
81
82 pub fn balance_commitment(&self) -> balance::Commitment {
83 self.balance().commit(self.balance_blinding)
84 }
85
86 pub fn balance(&self) -> Balance {
87 self.penalty
88 .balance_for_claim(self.unbonding_id(), self.unbonding_amount)
89 }
90}
91
92impl DomainType for UndelegateClaimPlan {
93 type Proto = pb::UndelegateClaimPlan;
94}
95
96impl From<UndelegateClaimPlan> for pb::UndelegateClaimPlan {
97 #[allow(deprecated)]
98 fn from(msg: UndelegateClaimPlan) -> Self {
99 Self {
100 validator_identity: Some(msg.validator_identity.into()),
101 start_epoch_index: 0,
102 penalty: Some(msg.penalty.into()),
103 unbonding_amount: Some(msg.unbonding_amount.into()),
104 unbonding_start_height: msg.unbonding_start_height,
105 balance_blinding: msg.balance_blinding.to_bytes().to_vec(),
106 proof_blinding_r: msg.proof_blinding_r.to_bytes().to_vec(),
107 proof_blinding_s: msg.proof_blinding_s.to_bytes().to_vec(),
108 }
109 }
110}
111
112impl TryFrom<pb::UndelegateClaimPlan> for UndelegateClaimPlan {
113 type Error = anyhow::Error;
114 fn try_from(msg: pb::UndelegateClaimPlan) -> Result<Self, Self::Error> {
115 let proof_blinding_r_bytes: [u8; 32] = msg
116 .proof_blinding_r
117 .try_into()
118 .map_err(|_| anyhow::anyhow!("malformed r in `UndelegateClaimPlan`"))?;
119 let proof_blinding_s_bytes: [u8; 32] = msg
120 .proof_blinding_s
121 .try_into()
122 .map_err(|_| anyhow::anyhow!("malformed s in `UndelegateClaimPlan`"))?;
123
124 Ok(Self {
125 validator_identity: msg
126 .validator_identity
127 .ok_or_else(|| anyhow::anyhow!("missing validator_identity"))?
128 .try_into()?,
129 penalty: msg
130 .penalty
131 .ok_or_else(|| anyhow::anyhow!("missing penalty"))?
132 .try_into()?,
133 unbonding_amount: msg
134 .unbonding_amount
135 .ok_or_else(|| anyhow::anyhow!("missing unbonding_amount"))?
136 .try_into()?,
137 balance_blinding: Fr::from_bytes_checked(
138 msg.balance_blinding
139 .as_slice()
140 .try_into()
141 .map_err(|_| anyhow::anyhow!("expected 32 bytes"))?,
142 )
143 .map_err(|_| anyhow::anyhow!("invalid balance_blinding"))?,
144 proof_blinding_r: Fq::from_bytes_checked(&proof_blinding_r_bytes)
145 .expect("proof_blinding_r malformed"),
146 proof_blinding_s: Fq::from_bytes_checked(&proof_blinding_s_bytes)
147 .expect("proof_blinding_s malformed"),
148 unbonding_start_height: msg.unbonding_start_height,
149 })
150 }
151}