squads_multisig_program/instructions/
batch_add_transaction.rs1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5use crate::TransactionMessage;
6
7#[derive(AnchorSerialize, AnchorDeserialize)]
8pub struct BatchAddTransactionArgs {
9 pub ephemeral_signers: u8,
11 pub transaction_message: Vec<u8>,
12}
13
14#[derive(Accounts)]
15#[instruction(args: BatchAddTransactionArgs)]
16pub struct BatchAddTransaction<'info> {
17 #[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 #[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 #[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 pub member: Signer<'info>,
68
69 #[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 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 require!(member.key() == batch.creator, MultisigError::Unauthorized);
97
98 require!(
100 matches!(proposal.status, ProposalStatus::Draft { .. }),
101 MultisigError::InvalidProposalStatus
102 );
103
104 Ok(())
107 }
108
109 #[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 batch.size = batch.size.checked_add(1).expect("overflow");
142
143 msg!("batch index: {}", batch.index);
145 msg!("batch size: {}", batch.size);
146
147 Ok(())
148 }
149}