penumbra_sdk_governance/action_handler/
deposit_claim.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use penumbra_sdk_proto::StateWriteProto as _;
5use penumbra_sdk_shielded_pool::component::AssetRegistry;
6
7use crate::action_handler::ActionHandler;
8use crate::component::{StateReadExt as _, StateWriteExt as _};
9use crate::event;
10use crate::{
11    proposal_state::Outcome, proposal_state::State as ProposalState, ProposalDepositClaim,
12    ProposalNft,
13};
14
15#[async_trait]
16impl ActionHandler for ProposalDepositClaim {
17    type CheckStatelessContext = ();
18    async fn check_stateless(&self, _context: ()) -> Result<()> {
19        // No stateless checks are required for this action (all checks require state access)
20        Ok(())
21    }
22
23    async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
24        // Any finished proposal can have its deposit claimed
25        state.check_proposal_claimable(self.proposal).await?;
26        // Check that the deposit amount matches the proposal being claimed
27        state
28            .check_proposal_claim_valid_deposit(self.proposal, self.deposit_amount)
29            .await?;
30
31        let ProposalDepositClaim {
32            proposal,
33            deposit_amount: _, // not needed to transition state; deposit is self-minted in tx
34            outcome: resupplied_outcome,
35        } = self;
36
37        // The only effect of doing a deposit claim is to state transition the proposal to claimed so it
38        // cannot be claimed again. The deposit amount is self-minted in the transaction (proof of
39        // deserving-ness is the supplied proposal NFT, which is burned in the transaction), so we don't
40        // need to distribute it here.
41
42        if let Some(ProposalState::Finished { outcome }) = state.proposal_state(*proposal).await? {
43            // This should be prevented by earlier checks, but replicating here JUST IN CASE!
44            if *resupplied_outcome != outcome.as_ref().map(|_| ()) {
45                anyhow::bail!(
46                    "proposal {} has outcome {:?}, but deposit claim has outcome {:?}",
47                    proposal,
48                    outcome,
49                    resupplied_outcome
50                );
51            }
52
53            // Register the denom for the claimed proposal NFT
54            state
55                .register_denom(
56                    &match &outcome {
57                        Outcome::Passed => ProposalNft::passed(*proposal),
58                        Outcome::Failed { .. } => ProposalNft::failed(*proposal),
59                        Outcome::Slashed { .. } => ProposalNft::slashed(*proposal),
60                    }
61                    .denom(),
62                )
63                .await;
64
65            // Set the proposal state to claimed
66            state.put_proposal_state(*proposal, ProposalState::Claimed { outcome });
67
68            state.record_proto(event::proposal_deposit_claim(self));
69        } else {
70            anyhow::bail!("proposal {} is not in finished state", proposal);
71        }
72
73        Ok(())
74    }
75}