squads_multisig_program/instructions/
proposal_vote.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5
6#[derive(AnchorSerialize, AnchorDeserialize)]
7pub struct ProposalVoteArgs {
8    pub memo: Option<String>,
9}
10
11#[derive(Accounts)]
12pub struct ProposalVote<'info> {
13    #[account(
14        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
15        bump = multisig.bump,
16    )]
17    pub multisig: Account<'info, Multisig>,
18
19    #[account(mut)]
20    pub member: Signer<'info>,
21
22    #[account(
23        mut,
24        seeds = [
25            SEED_PREFIX,
26            multisig.key().as_ref(),
27            SEED_TRANSACTION,
28            &proposal.transaction_index.to_le_bytes(),
29            SEED_PROPOSAL,
30        ],
31        bump = proposal.bump,
32    )]
33    pub proposal: Account<'info, Proposal>,
34}
35
36impl ProposalVote<'_> {
37    fn validate(&self, vote: Vote) -> Result<()> {
38        let Self {
39            multisig,
40            proposal,
41            member,
42            ..
43        } = self;
44
45        // member
46        require!(
47            multisig.is_member(member.key()).is_some(),
48            MultisigError::NotAMember
49        );
50        require!(
51            multisig.member_has_permission(member.key(), Permission::Vote),
52            MultisigError::Unauthorized
53        );
54
55        // proposal
56        match vote {
57            Vote::Approve | Vote::Reject => {
58                require!(
59                    matches!(proposal.status, ProposalStatus::Active { .. }),
60                    MultisigError::InvalidProposalStatus
61                );
62                // CANNOT approve or reject a stale proposal
63                require!(
64                    proposal.transaction_index > multisig.stale_transaction_index,
65                    MultisigError::StaleProposal
66                );
67            }
68            Vote::Cancel => {
69                require!(
70                    matches!(proposal.status, ProposalStatus::Approved { .. }),
71                    MultisigError::InvalidProposalStatus
72                );
73                // CAN cancel a stale proposal.
74            }
75        }
76
77        Ok(())
78    }
79
80    /// Approve a multisig proposal on behalf of the `member`.
81    /// The proposal must be `Active`.
82    #[access_control(ctx.accounts.validate(Vote::Approve))]
83    pub fn proposal_approve(ctx: Context<Self>, _args: ProposalVoteArgs) -> Result<()> {
84        let multisig = &mut ctx.accounts.multisig;
85        let proposal = &mut ctx.accounts.proposal;
86        let member = &mut ctx.accounts.member;
87
88        proposal.approve(member.key(), usize::from(multisig.threshold))?;
89
90        Ok(())
91    }
92
93    /// Reject a multisig proposal on behalf of the `member`.
94    /// The proposal must be `Active`.
95    #[access_control(ctx.accounts.validate(Vote::Reject))]
96    pub fn proposal_reject(ctx: Context<Self>, _args: ProposalVoteArgs) -> Result<()> {
97        let multisig = &mut ctx.accounts.multisig;
98        let proposal = &mut ctx.accounts.proposal;
99        let member = &mut ctx.accounts.member;
100
101        let cutoff = Multisig::cutoff(multisig);
102
103        proposal.reject(member.key(), cutoff)?;
104
105        Ok(())
106    }
107
108    /// Cancel a multisig proposal on behalf of the `member`.
109    /// The proposal must be `Approved`.
110    #[access_control(ctx.accounts.validate(Vote::Cancel))]
111    pub fn proposal_cancel(ctx: Context<Self>, _args: ProposalVoteArgs) -> Result<()> {
112        let multisig = &mut ctx.accounts.multisig;
113        let proposal = &mut ctx.accounts.proposal;
114        let member = &mut ctx.accounts.member;
115
116        proposal.cancel(member.key(), usize::from(multisig.threshold))?;
117
118        Ok(())
119    }
120}
121
122pub enum Vote {
123    Approve,
124    Reject,
125    Cancel,
126}