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 let transaction = &mut ctx.accounts.transaction;
112
113 let multisig_key = multisig.key();
114 let batch_key = batch.key();
115
116 let vault_seeds = &[
117 SEED_PREFIX,
118 multisig_key.as_ref(),
119 SEED_VAULT,
120 &batch.vault_index.to_le_bytes(),
121 &[batch.vault_bump],
122 ];
123
124 let transaction_message = &transaction.message;
125 let num_lookups = transaction_message.address_table_lookups.len();
126
127 let message_account_infos = ctx
128 .remaining_accounts
129 .get(num_lookups..)
130 .ok_or(MultisigError::InvalidNumberOfAccounts)?;
131 let address_lookup_table_account_infos = ctx
132 .remaining_accounts
133 .get(..num_lookups)
134 .ok_or(MultisigError::InvalidNumberOfAccounts)?;
135
136 let vault_pubkey = Pubkey::create_program_address(vault_seeds, ctx.program_id).unwrap();
137
138 let (ephemeral_signer_keys, ephemeral_signer_seeds) =
139 derive_ephemeral_signers(batch_key, &transaction.ephemeral_signer_bumps);
140
141 let executable_message = ExecutableTransactionMessage::new_validated(
142 transaction_message,
143 message_account_infos,
144 address_lookup_table_account_infos,
145 &vault_pubkey,
146 &ephemeral_signer_keys,
147 )?;
148
149 let protected_accounts = &[proposal.key(), batch_key];
150
151 executable_message.execute_message(
153 &vault_seeds
154 .iter()
155 .map(|seed| seed.to_vec())
156 .collect::<Vec<Vec<u8>>>(),
157 &ephemeral_signer_seeds,
158 protected_accounts,
159 )?;
160
161 batch.executed_transaction_index = batch
163 .executed_transaction_index
164 .checked_add(1)
165 .expect("overflow");
166
167 if batch.executed_transaction_index == batch.size {
169 proposal.status = ProposalStatus::Executed {
170 timestamp: Clock::get()?.unix_timestamp,
171 };
172 }
173
174 batch.invariant()?;
175
176 Ok(())
177 }
178}