squads_multisig_program/instructions/
batch_add_transaction.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5use crate::TransactionMessage;
6
7#[derive(AnchorSerialize, AnchorDeserialize)]
8pub struct BatchAddTransactionArgs {
9    /// Number of ephemeral signing PDAs required by the transaction.
10    pub ephemeral_signers: u8,
11    pub transaction_message: Vec<u8>,
12}
13
14#[derive(Accounts)]
15#[instruction(args: BatchAddTransactionArgs)]
16pub struct BatchAddTransaction<'info> {
17    /// Multisig account this batch belongs to.
18    #[account(
19        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
20        bump = multisig.bump,
21    )]
22    pub multisig: Account<'info, Multisig>,
23
24    /// The proposal account associated with the batch.
25    #[account(
26        seeds = [
27            SEED_PREFIX,
28            multisig.key().as_ref(),
29            SEED_TRANSACTION,
30            &batch.index.to_le_bytes(),
31            SEED_PROPOSAL,
32        ],
33        bump = proposal.bump,
34    )]
35    pub proposal: Account<'info, Proposal>,
36
37    #[account(
38        mut,
39        seeds = [
40            SEED_PREFIX,
41            multisig.key().as_ref(),
42            SEED_TRANSACTION,
43            &batch.index.to_le_bytes(),
44        ],
45        bump = batch.bump,
46    )]
47    pub batch: Account<'info, Batch>,
48
49    /// `VaultBatchTransaction` account to initialize and add to the `batch`.
50    #[account(
51        init,
52        payer = rent_payer,
53        space = VaultBatchTransaction::size(args.ephemeral_signers, &args.transaction_message)?,
54        seeds = [
55            SEED_PREFIX,
56            multisig.key().as_ref(),
57            SEED_TRANSACTION,
58            &batch.index.to_le_bytes(),
59            SEED_BATCH_TRANSACTION,
60            &batch.size.checked_add(1).unwrap().to_le_bytes(),
61        ],
62        bump
63    )]
64    pub transaction: Account<'info, VaultBatchTransaction>,
65
66    /// Member of the multisig.
67    pub member: Signer<'info>,
68
69    /// The payer for the batch transaction account rent.
70    #[account(mut)]
71    pub rent_payer: Signer<'info>,
72
73    pub system_program: Program<'info, System>,
74}
75
76impl BatchAddTransaction<'_> {
77    fn validate(&self) -> Result<()> {
78        let Self {
79            multisig,
80            member,
81            proposal,
82            batch,
83            ..
84        } = self;
85
86        // `member`
87        require!(
88            multisig.is_member(member.key()).is_some(),
89            MultisigError::NotAMember
90        );
91        require!(
92            multisig.member_has_permission(member.key(), Permission::Initiate),
93            MultisigError::Unauthorized
94        );
95        // Only batch creator can add transactions to it.
96        require!(member.key() == batch.creator, MultisigError::Unauthorized);
97
98        // `proposal`
99        require!(
100            matches!(proposal.status, ProposalStatus::Draft { .. }),
101            MultisigError::InvalidProposalStatus
102        );
103
104        // `batch` is validated by its seeds.
105
106        Ok(())
107    }
108
109    /// Add a transaction to the batch.
110    #[access_control(ctx.accounts.validate())]
111    pub fn batch_add_transaction(ctx: Context<Self>, args: BatchAddTransactionArgs) -> Result<()> {
112        let batch = &mut ctx.accounts.batch;
113        let transaction = &mut ctx.accounts.transaction;
114
115        let batch_key = batch.key();
116
117        let transaction_message =
118            TransactionMessage::deserialize(&mut args.transaction_message.as_slice())?;
119
120        let ephemeral_signer_bumps: Vec<u8> = (0..args.ephemeral_signers)
121            .map(|ephemeral_signer_index| {
122                let ephemeral_signer_seeds = &[
123                    SEED_PREFIX,
124                    batch_key.as_ref(),
125                    SEED_EPHEMERAL_SIGNER,
126                    &ephemeral_signer_index.to_le_bytes(),
127                ];
128
129                let (_, bump) =
130                    Pubkey::find_program_address(ephemeral_signer_seeds, ctx.program_id);
131
132                bump
133            })
134            .collect();
135
136        transaction.bump = ctx.bumps.transaction;
137        transaction.ephemeral_signer_bumps = ephemeral_signer_bumps;
138        transaction.message = transaction_message.try_into()?;
139
140        // Increment the batch size.
141        batch.size = batch.size.checked_add(1).expect("overflow");
142
143        // Logs for indexing.
144        msg!("batch index: {}", batch.index);
145        msg!("batch size: {}", batch.size);
146
147        Ok(())
148    }
149}