squads_multisig_program/instructions/
transaction_accounts_close.rs1use anchor_lang::prelude::*;
13
14use crate::errors::*;
15use crate::state::*;
16use crate::utils;
17
18#[derive(Accounts)]
19pub struct ConfigTransactionAccountsClose<'info> {
20 #[account(
21 seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
22 bump = multisig.bump,
23 constraint = multisig.rent_collector.is_some() @ MultisigError::RentReclamationDisabled,
24 )]
25 pub multisig: Account<'info, Multisig>,
26
27 #[account(
30 mut,
31 seeds = [
32 SEED_PREFIX,
33 multisig.key().as_ref(),
34 SEED_TRANSACTION,
35 &transaction.index.to_le_bytes(),
36 SEED_PROPOSAL,
37 ],
38 bump,
39 )]
40 pub proposal: AccountInfo<'info>,
41
42 #[account(
44 mut,
45 has_one = multisig @ MultisigError::TransactionForAnotherMultisig,
46 close = rent_collector
47 )]
48 pub transaction: Account<'info, ConfigTransaction>,
49
50 #[account(
53 mut,
54 address = multisig.rent_collector.unwrap().key() @ MultisigError::InvalidRentCollector,
55 )]
56 pub rent_collector: AccountInfo<'info>,
57
58 pub system_program: Program<'info, System>,
59}
60
61impl ConfigTransactionAccountsClose<'_> {
62 pub fn config_transaction_accounts_close(ctx: Context<Self>) -> Result<()> {
67 let multisig = &ctx.accounts.multisig;
68 let transaction = &ctx.accounts.transaction;
69 let proposal = &mut ctx.accounts.proposal;
70 let rent_collector = &ctx.accounts.rent_collector;
71
72 let is_stale = transaction.index <= multisig.stale_transaction_index;
73
74 let proposal_account = if proposal.data.borrow().is_empty() {
75 None
76 } else {
77 Some(Proposal::try_deserialize(
78 &mut &**proposal.data.borrow_mut(),
79 )?)
80 };
81
82 #[allow(deprecated)]
83 let can_close = if let Some(proposal_account) = &proposal_account {
84 match proposal_account.status {
85 ProposalStatus::Draft { .. } => is_stale,
88 ProposalStatus::Active { .. } => is_stale,
91 ProposalStatus::Approved { .. } => is_stale,
94 ProposalStatus::Rejected { .. } => true,
96 ProposalStatus::Executed { .. } => true,
98 ProposalStatus::Cancelled { .. } => true,
100 ProposalStatus::Executing => false,
102 }
103 } else {
104 is_stale
106 };
107
108 require!(can_close, MultisigError::InvalidProposalStatus);
109
110 if proposal_account.is_some() {
112 utils::close(
113 ctx.accounts.proposal.to_account_info(),
114 rent_collector.to_account_info(),
115 )?;
116 }
117
118 Ok(())
120 }
121}
122
123#[derive(Accounts)]
124pub struct VaultTransactionAccountsClose<'info> {
125 #[account(
126 seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
127 bump = multisig.bump,
128 constraint = multisig.rent_collector.is_some() @ MultisigError::RentReclamationDisabled,
129 )]
130 pub multisig: Account<'info, Multisig>,
131
132 #[account(
135 mut,
136 seeds = [
137 SEED_PREFIX,
138 multisig.key().as_ref(),
139 SEED_TRANSACTION,
140 &transaction.index.to_le_bytes(),
141 SEED_PROPOSAL,
142 ],
143 bump,
144 )]
145 pub proposal: AccountInfo<'info>,
146
147 #[account(
149 mut,
150 has_one = multisig @ MultisigError::TransactionForAnotherMultisig,
151 close = rent_collector
152 )]
153 pub transaction: Account<'info, VaultTransaction>,
154
155 #[account(
158 mut,
159 address = multisig.rent_collector.unwrap().key() @ MultisigError::InvalidRentCollector,
160 )]
161 pub rent_collector: AccountInfo<'info>,
162
163 pub system_program: Program<'info, System>,
164}
165
166impl VaultTransactionAccountsClose<'_> {
167 pub fn vault_transaction_accounts_close(
172 ctx: Context<VaultTransactionAccountsClose>,
173 ) -> Result<()> {
174 let multisig = &ctx.accounts.multisig;
175 let transaction = &ctx.accounts.transaction;
176 let proposal = &mut ctx.accounts.proposal;
177 let rent_collector = &ctx.accounts.rent_collector;
178
179 let is_stale = transaction.index <= multisig.stale_transaction_index;
180
181 let proposal_account = if proposal.data.borrow().is_empty() {
182 None
183 } else {
184 Some(Proposal::try_deserialize(
185 &mut &**proposal.data.borrow_mut(),
186 )?)
187 };
188
189 #[allow(deprecated)]
190 let can_close = if let Some(proposal_account) = &proposal_account {
191 match proposal_account.status {
192 ProposalStatus::Draft { .. } => is_stale,
195 ProposalStatus::Active { .. } => is_stale,
198 ProposalStatus::Approved { .. } => false,
201 ProposalStatus::Rejected { .. } => true,
203 ProposalStatus::Executed { .. } => true,
205 ProposalStatus::Cancelled { .. } => true,
207 ProposalStatus::Executing => false,
209 }
210 } else {
211 is_stale
213 };
214
215 require!(can_close, MultisigError::InvalidProposalStatus);
216
217 if proposal_account.is_some() {
219 utils::close(
220 ctx.accounts.proposal.to_account_info(),
221 rent_collector.to_account_info(),
222 )?;
223 }
224
225 Ok(())
227 }
228}
229
230#[derive(Accounts)]
232pub struct VaultBatchTransactionAccountClose<'info> {
233 #[account(
234 seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
235 bump = multisig.bump,
236 constraint = multisig.rent_collector.is_some() @ MultisigError::RentReclamationDisabled,
237 )]
238 pub multisig: Account<'info, Multisig>,
239
240 #[account(
241 has_one = multisig @ MultisigError::ProposalForAnotherMultisig,
242 )]
243 pub proposal: Account<'info, Proposal>,
244
245 #[account(
247 mut,
248 has_one = multisig @ MultisigError::TransactionForAnotherMultisig,
249 constraint = batch.index == proposal.transaction_index @ MultisigError::TransactionNotMatchingProposal,
250 )]
251 pub batch: Account<'info, Batch>,
252
253 #[account(
256 mut,
257 close = rent_collector,
258 )]
259 pub transaction: Account<'info, VaultBatchTransaction>,
260
261 #[account(
264 mut,
265 address = multisig.rent_collector.unwrap().key() @ MultisigError::InvalidRentCollector,
266 )]
267 pub rent_collector: AccountInfo<'info>,
268
269 pub system_program: Program<'info, System>,
270}
271
272impl VaultBatchTransactionAccountClose<'_> {
273 fn validate(&self) -> Result<()> {
274 let Self {
275 multisig,
276 proposal,
277 batch,
278 transaction,
279 ..
280 } = self;
281
282 let last_transaction_address = Pubkey::create_program_address(
287 &[
288 SEED_PREFIX,
289 multisig.key().as_ref(),
290 SEED_TRANSACTION,
291 &batch.index.to_le_bytes(),
292 SEED_BATCH_TRANSACTION,
293 &batch.size.to_le_bytes(),
295 &transaction.bump.to_le_bytes(),
297 ],
298 &crate::id(),
299 )
300 .map_err(|_| MultisigError::TransactionNotLastInBatch)?;
301
302 require_keys_eq!(
304 transaction.key(),
305 last_transaction_address,
306 MultisigError::TransactionNotLastInBatch
307 );
308
309 let is_proposal_stale = proposal.transaction_index <= multisig.stale_transaction_index;
310
311 #[allow(deprecated)]
312 let can_close = match proposal.status {
313 ProposalStatus::Draft { .. } => is_proposal_stale,
316 ProposalStatus::Active { .. } => is_proposal_stale,
319 ProposalStatus::Approved { .. } => false,
322 ProposalStatus::Rejected { .. } => true,
324 ProposalStatus::Executed { .. } => true,
326 ProposalStatus::Cancelled { .. } => true,
328 ProposalStatus::Executing => false,
330 };
331
332 require!(can_close, MultisigError::InvalidProposalStatus);
333
334 Ok(())
335 }
336
337 #[access_control(ctx.accounts.validate())]
344 pub fn vault_batch_transaction_account_close(ctx: Context<Self>) -> Result<()> {
345 let batch = &mut ctx.accounts.batch;
346
347 batch.size = batch.size.checked_sub(1).expect("overflow");
348
349 Ok(())
352 }
353}
354#[derive(Accounts)]
358pub struct BatchAccountsClose<'info> {
359 #[account(
360 seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
361 bump = multisig.bump,
362 constraint = multisig.rent_collector.is_some() @ MultisigError::RentReclamationDisabled,
363 )]
364 pub multisig: Account<'info, Multisig>,
365
366 #[account(
370 mut,
371 seeds = [
372 SEED_PREFIX,
373 multisig.key().as_ref(),
374 SEED_TRANSACTION,
375 &batch.index.to_le_bytes(),
376 SEED_PROPOSAL,
377 ],
378 bump,
379 )]
380 pub proposal: AccountInfo<'info>,
381
382 #[account(
384 mut,
385 has_one = multisig @ MultisigError::TransactionForAnotherMultisig,
386 close = rent_collector
387 )]
388 pub batch: Account<'info, Batch>,
389
390 #[account(
393 mut,
394 address = multisig.rent_collector.unwrap().key() @ MultisigError::InvalidRentCollector,
395 )]
396 pub rent_collector: AccountInfo<'info>,
397
398 pub system_program: Program<'info, System>,
399}
400
401impl BatchAccountsClose<'_> {
402 pub fn batch_accounts_close(ctx: Context<Self>) -> Result<()> {
408 let multisig = &ctx.accounts.multisig;
409 let batch = &ctx.accounts.batch;
410 let proposal = &mut ctx.accounts.proposal;
411 let rent_collector = &ctx.accounts.rent_collector;
412
413 let is_stale = batch.index <= multisig.stale_transaction_index;
414
415 let proposal_account = if proposal.data.borrow().is_empty() {
416 None
417 } else {
418 Some(Proposal::try_deserialize(
419 &mut &**proposal.data.borrow_mut(),
420 )?)
421 };
422
423 #[allow(deprecated)]
424 let can_close = if let Some(proposal_account) = &proposal_account {
425 match proposal_account.status {
426 ProposalStatus::Draft { .. } => is_stale,
429 ProposalStatus::Active { .. } => is_stale,
432 ProposalStatus::Approved { .. } => false,
435 ProposalStatus::Rejected { .. } => true,
437 ProposalStatus::Executed { .. } => true,
439 ProposalStatus::Cancelled { .. } => true,
441 ProposalStatus::Executing => false,
443 }
444 } else {
445 is_stale
447 };
448
449 require!(can_close, MultisigError::InvalidProposalStatus);
450
451 require_eq!(batch.size, 0, MultisigError::BatchNotEmpty);
453
454 if proposal_account.is_some() {
456 utils::close(
457 ctx.accounts.proposal.to_account_info(),
458 rent_collector.to_account_info(),
459 )?;
460 }
461
462 Ok(())
464 }
465}
466