squads_multisig_program/instructions/
batch_execute_transaction.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5use crate::utils::*;
6
7#[derive(Accounts)]
8pub struct BatchExecuteTransaction<'info> {
9    /// Multisig account this batch belongs to.
10    #[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    /// Member of the multisig.
17    pub member: Signer<'info>,
18
19    /// The proposal account associated with the batch.
20    /// If `transaction` is the last in the batch, the `proposal` status will be set to `Executed`.
21    #[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    /// Batch transaction to execute.
47    #[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    //
60    // `remaining_accounts` must include the following accounts in the exact order:
61    // 1. AddressLookupTable accounts in the order they appear in `message.address_table_lookups`.
62    // 2. Accounts in the order they appear in `message.account_keys`.
63    // 3. Accounts in the order they appear in `message.address_table_lookups`.
64}
65
66impl BatchExecuteTransaction<'_> {
67    fn validate(&self) -> Result<()> {
68        let Self {
69            multisig,
70            member,
71            proposal,
72            ..
73        } = self;
74
75        // `member`
76        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        // `proposal`
86        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        // Stale batch transaction proposals CAN be executed if they were approved
96        // before becoming stale, hence no check for staleness here.
97
98        // `batch` is validated by its seeds.
99
100        // `transaction` is validated by its seeds.
101
102        Ok(())
103    }
104
105    /// Execute a transaction from the batch.
106    #[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        // Execute the transaction message instructions one-by-one.
152        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        // Increment the executed transaction index.
162        batch.executed_transaction_index = batch
163            .executed_transaction_index
164            .checked_add(1)
165            .expect("overflow");
166
167        // If this is the last transaction in the batch, set the proposal status to `Executed`.
168        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}