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
36#[derive(Accounts)]
37pub struct ProposalCancelV2<'info> {
38 pub proposal_vote: ProposalVote<'info>,
40
41 pub system_program: Program<'info, System>,
42}
43
44impl ProposalVote<'_> {
45 fn validate(&self, vote: Vote) -> Result<()> {
46 let Self {
47 multisig,
48 proposal,
49 member,
50 ..
51 } = self;
52
53 require!(
55 multisig.is_member(member.key()).is_some(),
56 MultisigError::NotAMember
57 );
58 require!(
59 multisig.member_has_permission(member.key(), Permission::Vote),
60 MultisigError::Unauthorized
61 );
62
63 match vote {
65 Vote::Approve | Vote::Reject => {
66 require!(
67 matches!(proposal.status, ProposalStatus::Active { .. }),
68 MultisigError::InvalidProposalStatus
69 );
70 require!(
72 proposal.transaction_index > multisig.stale_transaction_index,
73 MultisigError::StaleProposal
74 );
75 }
76 Vote::Cancel => {
77 require!(
78 matches!(proposal.status, ProposalStatus::Approved { .. }),
79 MultisigError::InvalidProposalStatus
80 );
81 }
83 }
84
85 Ok(())
86 }
87
88 #[access_control(ctx.accounts.validate(Vote::Approve))]
91 pub fn proposal_approve(ctx: Context<Self>, _args: ProposalVoteArgs) -> Result<()> {
92 let multisig = &mut ctx.accounts.multisig;
93 let proposal = &mut ctx.accounts.proposal;
94 let member = &mut ctx.accounts.member;
95
96 proposal.approve(member.key(), usize::from(multisig.threshold))?;
97
98 Ok(())
99 }
100
101 #[access_control(ctx.accounts.validate(Vote::Reject))]
104 pub fn proposal_reject(ctx: Context<Self>, _args: ProposalVoteArgs) -> Result<()> {
105 let multisig = &mut ctx.accounts.multisig;
106 let proposal = &mut ctx.accounts.proposal;
107 let member = &mut ctx.accounts.member;
108
109 let cutoff = Multisig::cutoff(multisig);
110
111 proposal.reject(member.key(), cutoff)?;
112
113 Ok(())
114 }
115
116 #[access_control(ctx.accounts.validate(Vote::Cancel))]
119 pub fn proposal_cancel(ctx: Context<Self>, _args: ProposalVoteArgs) -> Result<()> {
120 let multisig = &mut ctx.accounts.multisig;
121 let proposal = &mut ctx.accounts.proposal;
122 let member = &mut ctx.accounts.member;
123
124 proposal
125 .cancelled
126 .retain(|k| multisig.is_member(*k).is_some());
127
128 proposal.cancel(member.key(), usize::from(multisig.threshold))?;
129
130 Ok(())
131 }
132}
133
134impl<'info> ProposalCancelV2<'info> {
135
136 pub fn proposal_cancel_v2(ctx: Context<'_, '_, 'info, 'info, Self>, _args: ProposalVoteArgs) -> Result<()> {
139 let multisig = &ctx.accounts.proposal_vote.multisig.clone();
141
142 let proposal_account_info = &ctx.accounts.proposal_vote.proposal.to_account_info();
144 let member_account_info = &ctx.accounts.proposal_vote.member.to_account_info();
145 let system_program_account_info = &ctx.accounts.system_program.to_account_info();
146
147 let cancel_context = Context::new(ctx.program_id, &mut ctx.accounts.proposal_vote, ctx.remaining_accounts, ctx.bumps.proposal_vote);
149
150 ProposalVote::proposal_cancel(cancel_context, _args)?;
152
153 Proposal::realloc_if_needed(
155 proposal_account_info.clone(),
156 multisig.members.len(),
157 Some(member_account_info.clone()),
158 Some(system_program_account_info.clone()),
159 )?;
160 Ok(())
161 }
162}
163
164pub enum Vote {
165 Approve,
166 Reject,
167 Cancel,
168}