squads_multisig_program/instructions/
proposal_create.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5
6#[derive(AnchorSerialize, AnchorDeserialize)]
7pub struct ProposalCreateArgs {
8    /// Index of the multisig transaction this proposal is associated with.
9    pub transaction_index: u64,
10    /// Whether the proposal should be initialized with status `Draft`.
11    pub draft: bool,
12}
13
14#[derive(Accounts)]
15#[instruction(args: ProposalCreateArgs)]
16pub struct ProposalCreate<'info> {
17    #[account(
18        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
19        bump = multisig.bump,
20    )]
21    pub multisig: Account<'info, Multisig>,
22
23    #[account(
24        init,
25        payer = rent_payer,
26        space = Proposal::size(multisig.members.len()),
27        seeds = [
28            SEED_PREFIX,
29            multisig.key().as_ref(),
30            SEED_TRANSACTION,
31            &args.transaction_index.to_le_bytes(),
32            SEED_PROPOSAL,
33        ],
34        bump
35    )]
36    pub proposal: Account<'info, Proposal>,
37
38    /// The member of the multisig that is creating the proposal.
39    pub creator: Signer<'info>,
40
41    /// The payer for the proposal account rent.
42    #[account(mut)]
43    pub rent_payer: Signer<'info>,
44
45    pub system_program: Program<'info, System>,
46}
47
48impl ProposalCreate<'_> {
49    fn validate(&self, args: &ProposalCreateArgs) -> Result<()> {
50        let Self {
51            multisig, creator, ..
52        } = self;
53        let creator_key = creator.key();
54
55        // args
56        // We can only create a proposal for an existing transaction.
57        require!(
58            args.transaction_index <= multisig.transaction_index,
59            MultisigError::InvalidTransactionIndex
60        );
61
62        // We can't create a proposal for a stale transaction.
63        require!(
64            args.transaction_index > multisig.stale_transaction_index,
65            MultisigError::StaleProposal
66        );
67
68        // creator
69        // Has to be a member.
70        require!(
71            self.multisig.is_member(self.creator.key()).is_some(),
72            MultisigError::NotAMember
73        );
74
75        // Must have at least one of the following permissions: Initiate or Vote.
76        require!(
77            self.multisig
78                .member_has_permission(creator_key, Permission::Initiate)
79                || self
80                    .multisig
81                    .member_has_permission(creator_key, Permission::Vote),
82            MultisigError::Unauthorized
83        );
84
85        Ok(())
86    }
87
88    /// Create a new multisig proposal.
89    #[access_control(ctx.accounts.validate(&args))]
90    pub fn proposal_create(ctx: Context<Self>, args: ProposalCreateArgs) -> Result<()> {
91        let proposal = &mut ctx.accounts.proposal;
92
93        proposal.multisig = ctx.accounts.multisig.key();
94        proposal.transaction_index = args.transaction_index;
95        proposal.status = if args.draft {
96            ProposalStatus::Draft {
97                timestamp: Clock::get()?.unix_timestamp,
98            }
99        } else {
100            ProposalStatus::Active {
101                timestamp: Clock::get()?.unix_timestamp,
102            }
103        };
104        proposal.bump = ctx.bumps.proposal;
105        proposal.approved = vec![];
106        proposal.rejected = vec![];
107        proposal.cancelled = vec![];
108
109        Ok(())
110    }
111}