penumbra_sdk_stake/component/action_handler/
undelegate_claim.rs1use anyhow::{ensure, Result};
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use penumbra_sdk_proof_params::CONVERT_PROOF_VERIFICATION_KEY;
5use penumbra_sdk_sct::component::clock::EpochRead;
6
7use crate::component::validator_handler::ValidatorDataRead;
8use crate::component::SlashingData;
9use crate::undelegate_claim::UndelegateClaimProofPublic;
10use crate::UndelegateClaim;
11use crate::{component::action_handler::ActionHandler, UnbondingToken};
12
13#[async_trait]
14impl ActionHandler for UndelegateClaim {
15 type CheckStatelessContext = ();
16 async fn check_stateless(&self, _context: ()) -> Result<()> {
17 let unbonding_id = UnbondingToken::new(
18 self.body.validator_identity,
19 self.body.unbonding_start_height,
20 )
21 .id();
22
23 self.proof.verify(
24 &CONVERT_PROOF_VERIFICATION_KEY,
25 UndelegateClaimProofPublic {
26 balance_commitment: self.body.balance_commitment,
27 unbonding_id,
28 penalty: self.body.penalty,
29 },
30 )?;
31
32 Ok(())
33 }
34
35 async fn check_and_execute<S: StateWrite>(&self, state: S) -> Result<()> {
36 let current_height = state.get_block_height().await?;
41 let unbonding_start_height = self.body.unbonding_start_height;
42 ensure!(
43 current_height >= unbonding_start_height,
44 "the unbonding start height must be less than or equal to the current height"
45 );
46
47 let allowed_unbonding_height = state
50 .compute_unbonding_height(&self.body.validator_identity, unbonding_start_height)
51 .await?
52 .unwrap_or(current_height);
53
54 let wait_blocks = allowed_unbonding_height.saturating_sub(current_height);
55
56 ensure!(
57 current_height >= allowed_unbonding_height,
58 "cannot claim unbonding tokens before height {} (currently at {}, wait {} blocks)",
59 allowed_unbonding_height,
60 current_height,
61 wait_blocks
62 );
63
64 let unbonding_epoch_start = state
65 .get_epoch_by_height(self.body.unbonding_start_height)
66 .await?;
67 let unbonding_epoch_end = state.get_epoch_by_height(allowed_unbonding_height).await?;
68
69 ensure!(
72 unbonding_epoch_end.index >= unbonding_epoch_start.index,
73 "unbonding epoch end must be greater than or equal to unbonding epoch start"
74 );
75
76 let expected_penalty = state
79 .compounded_penalty_over_range(
80 &self.body.validator_identity,
81 unbonding_epoch_start.index,
82 unbonding_epoch_end.index,
83 )
84 .await?;
85
86 ensure!(
87 self.body.penalty == expected_penalty,
88 "penalty (kept_rate: {}) does not match expected penalty (kept_rate: {})",
89 self.body.penalty.kept_rate(),
90 expected_penalty.kept_rate(),
91 );
92
93 Ok(())
97 }
98}