squads_multisig_program/instructions/
vault_transaction_create.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5use crate::utils::*;
6
7#[derive(AnchorSerialize, AnchorDeserialize)]
8pub struct VaultTransactionCreateArgs {
9    /// Index of the vault this transaction belongs to.
10    pub vault_index: u8,
11    /// Number of ephemeral signing PDAs required by the transaction.
12    pub ephemeral_signers: u8,
13    pub transaction_message: Vec<u8>,
14    pub memo: Option<String>,
15}
16
17#[derive(Accounts)]
18#[instruction(args: VaultTransactionCreateArgs)]
19pub struct VaultTransactionCreate<'info> {
20    #[account(
21        mut,
22        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
23        bump = multisig.bump,
24    )]
25    pub multisig: Account<'info, Multisig>,
26
27    #[account( 
28        init,
29        payer = rent_payer,
30        space = VaultTransaction::size(args.ephemeral_signers, &args.transaction_message)?,
31        seeds = [
32            SEED_PREFIX,
33            multisig.key().as_ref(),
34            SEED_TRANSACTION,
35            &multisig.transaction_index.checked_add(1).unwrap().to_le_bytes(),
36        ],
37        bump
38    )]
39    pub transaction: Account<'info, VaultTransaction>,
40
41    /// The member of the multisig that is creating the transaction.
42    pub creator: Signer<'info>,
43
44    /// The payer for the transaction account rent.
45    #[account(mut)]
46    pub rent_payer: Signer<'info>,
47
48    pub system_program: Program<'info, System>,
49}
50
51impl VaultTransactionCreate<'_> {
52    fn validate(&self) -> Result<()> {
53        let Self {
54            multisig, creator, ..
55        } = self;
56
57        // creator
58        require!(
59            multisig.is_member(creator.key()).is_some(),
60            MultisigError::NotAMember
61        );
62        require!(
63            multisig.member_has_permission(creator.key(), Permission::Initiate),
64            MultisigError::Unauthorized
65        );
66
67        Ok(())
68    }
69
70    /// Create a new vault transaction.
71    #[access_control(ctx.accounts.validate())]
72    pub fn vault_transaction_create(
73        ctx: Context<Self>,
74        args: VaultTransactionCreateArgs,
75    ) -> Result<()> {
76        let multisig = &mut ctx.accounts.multisig;
77        let transaction = &mut ctx.accounts.transaction;
78        let creator = &mut ctx.accounts.creator;
79
80        let transaction_message =
81            TransactionMessage::deserialize(&mut args.transaction_message.as_slice())?;
82
83        let multisig_key = multisig.key();
84        let transaction_key = transaction.key();
85
86        let vault_seeds = &[
87            SEED_PREFIX,
88            multisig_key.as_ref(),
89            SEED_VAULT,
90            &args.vault_index.to_le_bytes(),
91        ];
92        let (_, vault_bump) = Pubkey::find_program_address(vault_seeds, ctx.program_id);
93
94        let ephemeral_signer_bumps: Vec<u8> = (0..args.ephemeral_signers)
95            .map(|ephemeral_signer_index| {
96                let ephemeral_signer_seeds = &[
97                    SEED_PREFIX,
98                    transaction_key.as_ref(),
99                    SEED_EPHEMERAL_SIGNER,
100                    &ephemeral_signer_index.to_le_bytes(),
101                ];
102
103                let (_, bump) =
104                    Pubkey::find_program_address(ephemeral_signer_seeds, ctx.program_id);
105                bump
106            })
107            .collect();
108
109        // Increment the transaction index.
110        let transaction_index = multisig.transaction_index.checked_add(1).unwrap();
111
112        // Initialize the transaction fields.
113        transaction.multisig = multisig_key;
114        transaction.creator = creator.key();
115        transaction.index = transaction_index;
116        transaction.bump = ctx.bumps.transaction;
117        transaction.vault_index = args.vault_index;
118        transaction.vault_bump = vault_bump;
119        transaction.ephemeral_signer_bumps = ephemeral_signer_bumps;
120        transaction.message = transaction_message.try_into()?;
121
122        // Updated last transaction index in the multisig account.
123        multisig.transaction_index = transaction_index;
124
125        multisig.invariant()?;
126
127        // Logs for indexing.
128        msg!("transaction index: {}", transaction_index);
129
130        Ok(())
131    }
132}
133
134/// Unvalidated instruction data, must be treated as untrusted.
135#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
136pub struct TransactionMessage {
137    /// The number of signer pubkeys in the account_keys vec.
138    pub num_signers: u8,
139    /// The number of writable signer pubkeys in the account_keys vec.
140    pub num_writable_signers: u8,
141    /// The number of writable non-signer pubkeys in the account_keys vec.
142    pub num_writable_non_signers: u8,
143    /// The list of unique account public keys (including program IDs) that will be used in the provided instructions.
144    pub account_keys: SmallVec<u8, Pubkey>,
145    /// The list of instructions to execute.
146    pub instructions: SmallVec<u8, CompiledInstruction>,
147    /// List of address table lookups used to load additional accounts
148    /// for this transaction.
149    pub address_table_lookups: SmallVec<u8, MessageAddressTableLookup>,
150}
151
152// Concise serialization schema for instructions that make up transaction.
153#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
154pub struct CompiledInstruction {
155    pub program_id_index: u8,
156    /// Indices into the tx's `account_keys` list indicating which accounts to pass to the instruction.
157    pub account_indexes: SmallVec<u8, u8>,
158    /// Instruction data.
159    pub data: SmallVec<u16, u8>,
160}
161
162/// Address table lookups describe an on-chain address lookup table to use
163/// for loading more readonly and writable accounts in a single tx.
164#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
165pub struct MessageAddressTableLookup {
166    /// Address lookup table account key
167    pub account_key: Pubkey,
168    /// List of indexes used to load writable account addresses
169    pub writable_indexes: SmallVec<u8, u8>,
170    /// List of indexes used to load readonly account addresses
171    pub readonly_indexes: SmallVec<u8, u8>,
172}