penumbra_sdk_governance/action_handler/
delegator_vote.rs

1use anyhow::{Context, Result};
2use ark_ff::Zero;
3use async_trait::async_trait;
4use cnidarium::StateWrite;
5use decaf377::Fr;
6use penumbra_sdk_proof_params::DELEGATOR_VOTE_PROOF_VERIFICATION_KEY;
7use penumbra_sdk_proto::StateWriteProto as _;
8use penumbra_sdk_txhash::TransactionContext;
9
10use crate::{
11    event, DelegatorVote, DelegatorVoteBody, DelegatorVoteProofPublic,
12    {component::StateWriteExt, StateReadExt},
13};
14use cnidarium_component::ActionHandler;
15
16#[async_trait]
17impl ActionHandler for DelegatorVote {
18    type CheckStatelessContext = TransactionContext;
19
20    async fn check_stateless(&self, context: TransactionContext) -> Result<()> {
21        let DelegatorVote {
22            auth_sig,
23            proof,
24            body:
25                DelegatorVoteBody {
26                    start_position,
27                    nullifier,
28                    rk,
29                    value,
30                    // Unused in stateless checks:
31                    unbonded_amount: _,
32                    vote: _,     // Only used when executing the vote
33                    proposal: _, // Checked against the current open proposals statefully
34                },
35        } = self;
36
37        // 1. Check spend auth signature using provided spend auth key.
38        rk.verify(context.effect_hash.as_ref(), auth_sig)
39            .context("delegator vote auth signature failed to verify")?;
40
41        // 2. Verify the proof against the provided anchor and start position:
42        let public = DelegatorVoteProofPublic {
43            anchor: context.anchor,
44            balance_commitment: value.commit(Fr::zero()),
45            nullifier: *nullifier,
46            rk: *rk,
47            start_position: *start_position,
48        };
49        proof
50            .verify(&DELEGATOR_VOTE_PROOF_VERIFICATION_KEY, public)
51            .context("a delegator vote proof did not verify")?;
52
53        Ok(())
54    }
55
56    async fn check_and_execute<S: StateWrite>(&self, mut state: S) -> Result<()> {
57        let DelegatorVote {
58            body:
59                DelegatorVoteBody {
60                    proposal,
61                    vote,
62                    start_position,
63                    value,
64                    unbonded_amount,
65                    nullifier,
66                    rk: _, // We already used this to check the auth sig in stateless verification
67                },
68            auth_sig: _, // We already checked this in stateless verification
69            proof: _,    // We already checked this in stateless verification
70        } = self;
71
72        state.check_proposal_votable(*proposal).await?;
73        state
74            .check_proposal_started_at_position(*proposal, *start_position)
75            .await?;
76        state
77            .check_nullifier_unspent_before_start_block_height(*proposal, nullifier)
78            .await?;
79        state
80            .check_nullifier_unvoted_for_proposal(*proposal, nullifier)
81            .await?;
82        state
83            .check_unbonded_amount_correct_exchange_for_proposal(*proposal, value, unbonded_amount)
84            .await?;
85
86        state
87            .mark_nullifier_voted_on_proposal(*proposal, nullifier)
88            .await;
89        let identity_key = state.validator_by_delegation_asset(value.asset_id).await?;
90        state
91            .cast_delegator_vote(*proposal, identity_key, *vote, nullifier, *unbonded_amount)
92            .await?;
93
94        state.record_proto(event::delegator_vote(self, &identity_key));
95
96        Ok(())
97    }
98}