squads_multisig_program/instructions/
batch_execute_transaction.rs1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5use crate::utils::*;
6
7#[derive(Accounts)]
8pub struct BatchExecuteTransaction<'info> {
9 #[account(
11 seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
12 bump = multisig.bump,
13 )]
14 pub multisig: Account<'info, Multisig>,
15
16 pub member: Signer<'info>,
18
19 #[account(
22 mut,
23 seeds = [
24 SEED_PREFIX,
25 multisig.key().as_ref(),
26 SEED_TRANSACTION,
27 &batch.index.to_le_bytes(),
28 SEED_PROPOSAL,
29 ],
30 bump = proposal.bump,
31 )]
32 pub proposal: Account<'info, Proposal>,
33
34 #[account(
35 mut,
36 seeds = [
37 SEED_PREFIX,
38 multisig.key().as_ref(),
39 SEED_TRANSACTION,
40 &batch.index.to_le_bytes(),
41 ],
42 bump = batch.bump,
43 )]
44 pub batch: Account<'info, Batch>,
45
46 #[account(
48 seeds = [
49 SEED_PREFIX,
50 multisig.key().as_ref(),
51 SEED_TRANSACTION,
52 &batch.index.to_le_bytes(),
53 SEED_BATCH_TRANSACTION,
54 &batch.executed_transaction_index.checked_add(1).unwrap().to_le_bytes(),
55 ],
56 bump = transaction.bump,
57 )]
58 pub transaction: Account<'info, VaultBatchTransaction>,
59 }
65
66impl BatchExecuteTransaction<'_> {
67 fn validate(&self) -> Result<()> {
68 let Self {
69 multisig,
70 member,
71 proposal,
72 ..
73 } = self;
74
75 require!(
77 multisig.is_member(member.key()).is_some(),
78 MultisigError::NotAMember
79 );
80 require!(
81 multisig.member_has_permission(member.key(), Permission::Execute),
82 MultisigError::Unauthorized
83 );
84
85 match proposal.status {
87 ProposalStatus::Approved { timestamp } => {
88 require!(
89 Clock::get()?.unix_timestamp - timestamp >= i64::from(multisig.time_lock),
90 MultisigError::TimeLockNotReleased
91 );
92 }
93 _ => return err!(MultisigError::InvalidProposalStatus),
94 };
95 Ok(())
103 }
104
105 #[access_control(ctx.accounts.validate())]
107 pub fn batch_execute_transaction(ctx: Context<Self>) -> Result<()> {
108 let multisig = &mut ctx.accounts.multisig;
109 let proposal = &mut ctx.accounts.proposal;
110 let batch = &mut ctx.accounts.batch;
111
112 let transaction = ctx.accounts.transaction.take();
117
118 let multisig_key = multisig.key();
119 let batch_key = batch.key();
120
121 let vault_seeds = &[
122 SEED_PREFIX,
123 multisig_key.as_ref(),
124 SEED_VAULT,
125 &batch.vault_index.to_le_bytes(),
126 &[batch.vault_bump],
127 ];
128
129 let transaction_message = transaction.message;
130 let num_lookups = transaction_message.address_table_lookups.len();
131
132 let message_account_infos = ctx
133 .remaining_accounts
134 .get(num_lookups..)
135 .ok_or(MultisigError::InvalidNumberOfAccounts)?;
136 let address_lookup_table_account_infos = ctx
137 .remaining_accounts
138 .get(..num_lookups)
139 .ok_or(MultisigError::InvalidNumberOfAccounts)?;
140
141 let vault_pubkey = Pubkey::create_program_address(vault_seeds, ctx.program_id).unwrap();
142
143 let (ephemeral_signer_keys, ephemeral_signer_seeds) =
144 derive_ephemeral_signers(batch_key, &transaction.ephemeral_signer_bumps);
145
146 let executable_message = ExecutableTransactionMessage::new_validated(
147 transaction_message,
148 message_account_infos,
149 address_lookup_table_account_infos,
150 &vault_pubkey,
151 &ephemeral_signer_keys,
152 )?;
153
154 let protected_accounts = &[proposal.key(), batch_key];
155
156 executable_message.execute_message(
163 vault_seeds,
164 &ephemeral_signer_seeds,
165 protected_accounts,
166 )?;
167
168 batch.executed_transaction_index = batch
170 .executed_transaction_index
171 .checked_add(1)
172 .expect("overflow");
173
174 if batch.executed_transaction_index == batch.size {
176 proposal.status = ProposalStatus::Executed {
177 timestamp: Clock::get()?.unix_timestamp,
178 };
179 }
180
181 batch.invariant()?;
182
183 Ok(())
184 }
185}