Skip to main content

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
92        // NOTE: After `take()` is called, the VaultTransaction is reduced to
93        // its default empty value, which means it should no longer be referenced or
94        // used after this point to avoid faulty behavior.
95        // Instead only make use of the returned `transaction` value.
96        let transaction = ctx.accounts.transaction.take();
97
98        let multisig_key = multisig.key();
99        let transaction_key = ctx.accounts.transaction.key();
100
101        let vault_seeds = &[
102            SEED_PREFIX,
103            multisig_key.as_ref(),
104            SEED_VAULT,
105            &transaction.vault_index.to_le_bytes(),
106            &[transaction.vault_bump],
107        ];
108
109        let transaction_message = transaction.message;
110        let num_lookups = transaction_message.address_table_lookups.len();
111
112        let message_account_infos = ctx
113            .remaining_accounts
114            .get(num_lookups..)
115            .ok_or(MultisigError::InvalidNumberOfAccounts)?;
116        let address_lookup_table_account_infos = ctx
117            .remaining_accounts
118            .get(..num_lookups)
119            .ok_or(MultisigError::InvalidNumberOfAccounts)?;
120
121        let vault_pubkey = Pubkey::create_program_address(vault_seeds, ctx.program_id).unwrap();
122
123        let (ephemeral_signer_keys, ephemeral_signer_seeds) =
124            derive_ephemeral_signers(transaction_key, &transaction.ephemeral_signer_bumps);
125
126        let executable_message = ExecutableTransactionMessage::new_validated(
127            transaction_message,
128            message_account_infos,
129            address_lookup_table_account_infos,
130            &vault_pubkey,
131            &ephemeral_signer_keys,
132        )?;
133
134        let protected_accounts = &[proposal.key()];
135
136        // Execute the transaction message instructions one-by-one.
137        // NOTE: `execute_message()` calls `self.to_instructions_and_accounts()`
138        // which in turn calls `take()` on
139        // `self.message.instructions`, therefore after this point no more
140        // references or usages of `self.message` should be made to avoid
141        // faulty behavior.
142        executable_message.execute_message(
143            vault_seeds,
144            &ephemeral_signer_seeds,
145            protected_accounts,
146        )?;
147
148        // Mark the proposal as executed.
149        proposal.status = ProposalStatus::Executed {
150            timestamp: Clock::get()?.unix_timestamp,
151        };
152
153        Ok(())
154    }
155}