squads_multisig_program/instructions/
vault_transaction_execute.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5use crate::utils::*;
6
7#[derive(Accounts)]
8pub struct VaultTransactionExecute<'info> {
9    #[account(
10        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
11        bump = multisig.bump,
12    )]
13    pub multisig: Box<Account<'info, Multisig>>,
14
15    /// The proposal account associated with the transaction.
16    #[account(
17        mut,
18        seeds = [
19            SEED_PREFIX,
20            multisig.key().as_ref(),
21            SEED_TRANSACTION,
22            &transaction.index.to_le_bytes(),
23            SEED_PROPOSAL,
24        ],
25        bump = proposal.bump,
26    )]
27    pub proposal: Account<'info, Proposal>,
28
29    /// The transaction to execute.
30    #[account(
31        seeds = [
32            SEED_PREFIX,
33            multisig.key().as_ref(),
34            SEED_TRANSACTION,
35            &transaction.index.to_le_bytes(),
36        ],
37        bump = transaction.bump,
38    )]
39    pub transaction: Account<'info, VaultTransaction>,
40
41    pub member: Signer<'info>,
42    // `remaining_accounts` must include the following accounts in the exact order:
43    // 1. AddressLookupTable accounts in the order they appear in `message.address_table_lookups`.
44    // 2. Accounts in the order they appear in `message.account_keys`.
45    // 3. Accounts in the order they appear in `message.address_table_lookups`.
46}
47
48impl VaultTransactionExecute<'_> {
49    fn validate(&self) -> Result<()> {
50        let Self {
51            multisig,
52            proposal,
53            member,
54            ..
55        } = self;
56
57        // member
58        require!(
59            multisig.is_member(member.key()).is_some(),
60            MultisigError::NotAMember
61        );
62        require!(
63            multisig.member_has_permission(member.key(), Permission::Execute),
64            MultisigError::Unauthorized
65        );
66
67        // proposal
68        match proposal.status {
69            ProposalStatus::Approved { timestamp } => {
70                require!(
71                    Clock::get()?.unix_timestamp - timestamp >= i64::from(multisig.time_lock),
72                    MultisigError::TimeLockNotReleased
73                );
74            }
75            _ => return err!(MultisigError::InvalidProposalStatus),
76        }
77        // Stale vault transaction proposals CAN be executed if they were approved
78        // before becoming stale, hence no check for staleness here.
79
80        // `transaction` is validated by its seeds.
81
82        Ok(())
83    }
84
85    /// Execute the multisig transaction.
86    /// The transaction must be `Approved`.
87    #[access_control(ctx.accounts.validate())]
88    pub fn vault_transaction_execute(ctx: Context<Self>) -> Result<()> {
89        let multisig = &mut ctx.accounts.multisig;
90        let proposal = &mut ctx.accounts.proposal;
91        let transaction = &mut ctx.accounts.transaction;
92
93        let multisig_key = multisig.key();
94        let transaction_key = transaction.key();
95
96        let vault_seeds = &[
97            SEED_PREFIX,
98            multisig_key.as_ref(),
99            SEED_VAULT,
100            &transaction.vault_index.to_le_bytes(),
101            &[transaction.vault_bump],
102        ];
103
104        let transaction_message = &transaction.message;
105        let num_lookups = transaction_message.address_table_lookups.len();
106
107        let message_account_infos = ctx
108            .remaining_accounts
109            .get(num_lookups..)
110            .ok_or(MultisigError::InvalidNumberOfAccounts)?;
111        let address_lookup_table_account_infos = ctx
112            .remaining_accounts
113            .get(..num_lookups)
114            .ok_or(MultisigError::InvalidNumberOfAccounts)?;
115
116        let vault_pubkey = Pubkey::create_program_address(vault_seeds, ctx.program_id).unwrap();
117
118        let (ephemeral_signer_keys, ephemeral_signer_seeds) =
119            derive_ephemeral_signers(transaction_key, &transaction.ephemeral_signer_bumps);
120
121        let executable_message = ExecutableTransactionMessage::new_validated(
122            transaction_message,
123            message_account_infos,
124            address_lookup_table_account_infos,
125            &vault_pubkey,
126            &ephemeral_signer_keys,
127        )?;
128
129        let protected_accounts = &[proposal.key()];
130
131        // Execute the transaction message instructions one-by-one.
132        executable_message.execute_message(
133            &vault_seeds
134                .iter()
135                .map(|seed| seed.to_vec())
136                .collect::<Vec<Vec<u8>>>(),
137            &ephemeral_signer_seeds,
138            protected_accounts,
139        )?;
140
141        // Mark the proposal as executed.
142        proposal.status = ProposalStatus::Executed {
143            timestamp: Clock::get()?.unix_timestamp,
144        };
145
146        Ok(())
147    }
148}