penumbra_sdk_governance/action_handler/
withdraw.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::{
8    action_handler::ActionHandler,
9    component::{StateReadExt as _, StateWriteExt},
10    event,
11    proposal_state::State as ProposalState,
12    ProposalNft, ProposalWithdraw,
13};
14
15#[async_trait]
16impl ActionHandler for ProposalWithdraw {
17    type CheckStatelessContext = ();
18    async fn check_stateless(&self, _context: ()) -> Result<()> {
19        // Enforce a maximum length on proposal withdrawal reasons; 80 characters seems reasonable.
20        const PROPOSAL_WITHDRAWAL_REASON_LIMIT: usize = 80;
21
22        if self.reason.len() > PROPOSAL_WITHDRAWAL_REASON_LIMIT {
23            anyhow::bail!(
24                "proposal withdrawal reason must fit within {PROPOSAL_WITHDRAWAL_REASON_LIMIT} characters"
25            );
26        }
27
28        Ok(())
29    }
30
31    async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
32        // Any votable proposal can be withdrawn
33        state.check_proposal_votable(self.proposal).await?;
34
35        let ProposalWithdraw { proposal, reason } = self;
36
37        // Update the proposal state to withdrawn
38        state.put_proposal_state(
39            *proposal,
40            ProposalState::Withdrawn {
41                reason: reason.clone(),
42            },
43        );
44
45        // Register the denom for the withdrawn proposal NFT
46        state
47            .register_denom(&ProposalNft::unbonding_deposit(*proposal).denom())
48            .await;
49
50        state.record_proto(event::proposal_withdraw(self));
51
52        tracing::debug!(proposal = %proposal, "withdrew proposal");
53
54        Ok(())
55    }
56}