squads_multisig_program/state/
proposal.rs1#![allow(deprecated)]
2use anchor_lang::prelude::*;
3
4use crate::errors::*;
5use crate::id;
6
7use anchor_lang::system_program;
8
9#[account]
13pub struct Proposal {
14 pub multisig: Pubkey,
16 pub transaction_index: u64,
18 pub status: ProposalStatus,
20 pub bump: u8,
22 pub approved: Vec<Pubkey>,
24 pub rejected: Vec<Pubkey>,
26 pub cancelled: Vec<Pubkey>,
28}
29
30impl Proposal {
31 pub fn size(members_len: usize) -> usize {
32 8 + 32 + 8 + 1 + 8 + 1 + (4 + (members_len * 32)) + (4 + (members_len * 32)) + (4 + (members_len * 32)) }
42
43 pub fn approve(&mut self, member: Pubkey, threshold: usize) -> Result<()> {
45 if let Some(vote_index) = self.has_voted_reject(member.key()) {
47 self.remove_rejection_vote(vote_index);
48 }
49
50 match self.approved.binary_search(&member) {
52 Ok(_) => return err!(MultisigError::AlreadyApproved),
53 Err(pos) => self.approved.insert(pos, member),
54 };
55
56 if self.approved.len() >= threshold {
58 self.status = ProposalStatus::Approved {
59 timestamp: Clock::get()?.unix_timestamp,
60 };
61 }
62
63 Ok(())
64 }
65
66 pub fn reject(&mut self, member: Pubkey, cutoff: usize) -> Result<()> {
68 if let Some(vote_index) = self.has_voted_approve(member.key()) {
70 self.remove_approval_vote(vote_index);
71 }
72
73 match self.rejected.binary_search(&member) {
75 Ok(_) => return err!(MultisigError::AlreadyRejected),
76 Err(pos) => self.rejected.insert(pos, member),
77 };
78
79 if self.rejected.len() >= cutoff {
81 self.status = ProposalStatus::Rejected {
82 timestamp: Clock::get()?.unix_timestamp,
83 };
84 }
85
86 Ok(())
87 }
88
89 pub fn cancel(&mut self, member: Pubkey, threshold: usize) -> Result<()> {
91 match self.cancelled.binary_search(&member) {
93 Ok(_) => return err!(MultisigError::AlreadyCancelled),
94 Err(pos) => self.cancelled.insert(pos, member),
95 };
96
97 if self.cancelled.len() >= threshold {
99 self.status = ProposalStatus::Cancelled {
100 timestamp: Clock::get()?.unix_timestamp,
101 };
102 }
103
104 Ok(())
105 }
106
107 fn has_voted_approve(&self, member: Pubkey) -> Option<usize> {
110 self.approved.binary_search(&member).ok()
111 }
112
113 fn has_voted_reject(&self, member: Pubkey) -> Option<usize> {
116 self.rejected.binary_search(&member).ok()
117 }
118
119 fn remove_rejection_vote(&mut self, index: usize) {
121 self.rejected.remove(index);
122 }
123
124 fn remove_approval_vote(&mut self, index: usize) {
126 self.approved.remove(index);
127 }
128
129 pub fn realloc_if_needed<'a>(
133 proposal: AccountInfo<'a>,
134 members_length: usize,
135 rent_payer: Option<AccountInfo<'a>>,
136 system_program: Option<AccountInfo<'a>>,
137 ) -> Result<bool> {
138 require_keys_eq!(*proposal.owner, id(), MultisigError::IllegalAccountOwner);
140
141 let current_account_size = proposal.data.borrow().len();
142 let account_size_to_fit_members = Proposal::size(members_length);
143
144 if current_account_size >= account_size_to_fit_members {
146 return Ok(false);
147 }
148
149 AccountInfo::realloc(&proposal, account_size_to_fit_members, false)?;
151
152 let rent_exempt_lamports = Rent::get()
154 .unwrap()
155 .minimum_balance(account_size_to_fit_members)
156 .max(1);
157 let top_up_lamports =
158 rent_exempt_lamports.saturating_sub(proposal.to_account_info().lamports());
159
160 if top_up_lamports > 0 {
161 let system_program = system_program.ok_or(MultisigError::MissingAccount)?;
162 require_keys_eq!(
163 *system_program.key,
164 system_program::ID,
165 MultisigError::InvalidAccount
166 );
167
168 let rent_payer = rent_payer.ok_or(MultisigError::MissingAccount)?;
169
170 system_program::transfer(
171 CpiContext::new(
172 system_program,
173 system_program::Transfer {
174 from: rent_payer,
175 to: proposal,
176 },
177 ),
178 top_up_lamports,
179 )?;
180 }
181
182 Ok(true)
183 }
184}
185
186#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, Debug)]
189#[non_exhaustive]
190pub enum ProposalStatus {
191 Draft { timestamp: i64 },
193 Active { timestamp: i64 },
195 Rejected { timestamp: i64 },
197 Approved { timestamp: i64 },
199 #[deprecated(
201 note = "This status used to be used to prevent reentrancy attacks. It is no longer needed."
202 )]
203 Executing,
204 Executed { timestamp: i64 },
206 Cancelled { timestamp: i64 },
208}