squads_multisig_program/state/
multisig.rs1use std::cmp::max;
2
3use anchor_lang::prelude::*;
4use anchor_lang::system_program;
5
6use crate::errors::*;
7use crate::id;
8
9pub const MAX_TIME_LOCK: u32 = 3 * 30 * 24 * 60 * 60; #[account]
12pub struct Multisig {
13 pub create_key: Pubkey,
15 pub config_authority: Pubkey,
25 pub threshold: u16,
27 pub time_lock: u32,
29 pub transaction_index: u64,
31 pub stale_transaction_index: u64,
34 pub rent_collector: Option<Pubkey>,
37 pub bump: u8,
39 pub members: Vec<Member>,
41}
42
43impl Multisig {
44 pub fn size(members_length: usize) -> usize {
45 8 + 32 + 32 + 2 + 4 + 8 + 8 + 1 + 32 + 1 + 4 + members_length * Member::INIT_SPACE }
58
59 pub fn num_voters(members: &[Member]) -> usize {
60 members
61 .iter()
62 .filter(|m| m.permissions.has(Permission::Vote))
63 .count()
64 }
65
66 pub fn num_proposers(members: &[Member]) -> usize {
67 members
68 .iter()
69 .filter(|m| m.permissions.has(Permission::Initiate))
70 .count()
71 }
72
73 pub fn num_executors(members: &[Member]) -> usize {
74 members
75 .iter()
76 .filter(|m| m.permissions.has(Permission::Execute))
77 .count()
78 }
79
80 pub fn realloc_if_needed<'a>(
83 multisig: AccountInfo<'a>,
84 members_length: usize,
85 rent_payer: Option<AccountInfo<'a>>,
86 system_program: Option<AccountInfo<'a>>,
87 ) -> Result<bool> {
88 require_keys_eq!(*multisig.owner, id(), MultisigError::IllegalAccountOwner);
90
91 let current_account_size = multisig.data.borrow().len();
92 let account_size_to_fit_members = Multisig::size(members_length);
93
94 if current_account_size >= account_size_to_fit_members {
96 return Ok(false);
97 }
98
99 let new_size = max(
100 current_account_size + (10 * Member::INIT_SPACE), account_size_to_fit_members,
102 );
103 AccountInfo::realloc(&multisig, new_size, false)?;
105
106 let rent_exempt_lamports = Rent::get().unwrap().minimum_balance(new_size).max(1);
108 let top_up_lamports =
109 rent_exempt_lamports.saturating_sub(multisig.to_account_info().lamports());
110
111 if top_up_lamports > 0 {
112 let system_program = system_program.ok_or(MultisigError::MissingAccount)?;
113 require_keys_eq!(
114 *system_program.key,
115 system_program::ID,
116 MultisigError::InvalidAccount
117 );
118
119 let rent_payer = rent_payer.ok_or(MultisigError::MissingAccount)?;
120
121 system_program::transfer(
122 CpiContext::new(
123 system_program,
124 system_program::Transfer {
125 from: rent_payer,
126 to: multisig,
127 },
128 ),
129 top_up_lamports,
130 )?;
131 }
132
133 Ok(true)
134 }
135
136 pub fn invariant(&self) -> Result<()> {
139 let Self {
140 threshold,
141 members,
142 transaction_index,
143 stale_transaction_index,
144 ..
145 } = self;
146 require!(
148 members.len() <= usize::from(u16::MAX),
149 MultisigError::TooManyMembers
150 );
151
152 let has_duplicates = members.windows(2).any(|win| win[0].key == win[1].key);
154 require!(!has_duplicates, MultisigError::DuplicateMember);
155
156 require!(
158 members.iter().all(|m| m.permissions.mask < 8), MultisigError::UnknownPermission
160 );
161
162 let num_proposers = Self::num_proposers(members);
164 require!(num_proposers > 0, MultisigError::NoProposers);
165
166 let num_executors = Self::num_executors(members);
168 require!(num_executors > 0, MultisigError::NoExecutors);
169
170 let num_voters = Self::num_voters(members);
172 require!(num_voters > 0, MultisigError::NoVoters);
173
174 require!(*threshold > 0, MultisigError::InvalidThreshold);
176
177 require!(
179 usize::from(*threshold) <= num_voters,
180 MultisigError::InvalidThreshold
181 );
182
183 require!(
185 stale_transaction_index <= transaction_index,
186 MultisigError::InvalidStaleTransactionIndex
187 );
188
189 require!(
191 self.time_lock <= MAX_TIME_LOCK,
192 MultisigError::TimeLockExceedsMaxAllowed
193 );
194
195 Ok(())
196 }
197
198 pub fn invalidate_prior_transactions(&mut self) {
201 self.stale_transaction_index = self.transaction_index;
202 }
203
204 pub fn is_member(&self, member_pubkey: Pubkey) -> Option<usize> {
207 self.members
208 .binary_search_by_key(&member_pubkey, |m| m.key)
209 .ok()
210 }
211
212 pub fn member_has_permission(&self, member_pubkey: Pubkey, permission: Permission) -> bool {
213 match self.is_member(member_pubkey) {
214 Some(index) => self.members[index].permissions.has(permission),
215 _ => false,
216 }
217 }
218
219 pub fn cutoff(&self) -> usize {
223 Self::num_voters(&self.members)
224 .checked_sub(usize::from(self.threshold))
225 .unwrap()
226 .checked_add(1)
227 .unwrap()
228 }
229
230 pub fn add_member(&mut self, new_member: Member) {
232 self.members.push(new_member);
233 self.members.sort_by_key(|m| m.key);
234 }
235
236 pub fn remove_member(&mut self, member_pubkey: Pubkey) -> Result<()> {
241 let old_member_index = match self.is_member(member_pubkey) {
242 Some(old_member_index) => old_member_index,
243 None => return err!(MultisigError::NotAMember),
244 };
245
246 self.members.remove(old_member_index);
247
248 Ok(())
249 }
250}
251
252#[derive(AnchorDeserialize, AnchorSerialize, InitSpace, Eq, PartialEq, Clone)]
253pub struct Member {
254 pub key: Pubkey,
255 pub permissions: Permissions,
256}
257
258#[derive(Clone, Copy)]
259pub enum Permission {
260 Initiate = 1 << 0,
261 Vote = 1 << 1,
262 Execute = 1 << 2,
263}
264
265#[derive(
267 AnchorSerialize, AnchorDeserialize, InitSpace, Eq, PartialEq, Clone, Copy, Default, Debug,
268)]
269pub struct Permissions {
270 pub mask: u8,
271}
272
273impl Permissions {
274 pub fn from_vec(permissions: &[Permission]) -> Self {
276 let mut mask = 0;
277 for permission in permissions {
278 mask |= *permission as u8;
279 }
280 Self { mask }
281 }
282
283 pub fn has(&self, permission: Permission) -> bool {
284 self.mask & (permission as u8) != 0
285 }
286}