squads_multisig_program/instructions/
proposal_vote.rs1use 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 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 match vote {
57 Vote::Approve | Vote::Reject => {
58 require!(
59 matches!(proposal.status, ProposalStatus::Active { .. }),
60 MultisigError::InvalidProposalStatus
61 );
62 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 }
75 }
76
77 Ok(())
78 }
79
80 #[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 #[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 #[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}