squads_multisig_program/instructions/
multisig_config.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5
6#[derive(AnchorSerialize, AnchorDeserialize)]
7pub struct MultisigAddMemberArgs {
8    pub new_member: Member,
9    /// Memo is used for indexing only.
10    pub memo: Option<String>,
11}
12
13#[derive(AnchorSerialize, AnchorDeserialize)]
14pub struct MultisigRemoveMemberArgs {
15    pub old_member: Pubkey,
16    /// Memo is used for indexing only.
17    pub memo: Option<String>,
18}
19
20#[derive(AnchorSerialize, AnchorDeserialize)]
21pub struct MultisigChangeThresholdArgs {
22    pub new_threshold: u16,
23    /// Memo is used for indexing only.
24    pub memo: Option<String>,
25}
26
27#[derive(AnchorSerialize, AnchorDeserialize)]
28pub struct MultisigSetTimeLockArgs {
29    pub time_lock: u32,
30    /// Memo is used for indexing only.
31    pub memo: Option<String>,
32}
33
34#[derive(AnchorSerialize, AnchorDeserialize)]
35pub struct MultisigSetConfigAuthorityArgs {
36    pub config_authority: Pubkey,
37    /// Memo is used for indexing only.
38    pub memo: Option<String>,
39}
40
41#[derive(AnchorSerialize, AnchorDeserialize)]
42pub struct MultisigSetRentCollectorArgs {
43    pub rent_collector: Option<Pubkey>,
44    /// Memo is used for indexing only.
45    pub memo: Option<String>,
46}
47
48#[derive(Accounts)]
49pub struct MultisigConfig<'info> {
50    #[account(
51        mut,
52        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
53        bump = multisig.bump,
54    )]
55    multisig: Account<'info, Multisig>,
56
57    /// Multisig `config_authority` that must authorize the configuration change.
58    pub config_authority: Signer<'info>,
59
60    /// The account that will be charged or credited in case the multisig account needs to reallocate space,
61    /// for example when adding a new member or a spending limit.
62    /// This is usually the same as `config_authority`, but can be a different account if needed.
63    #[account(mut)]
64    pub rent_payer: Option<Signer<'info>>,
65
66    /// We might need it in case reallocation is needed.
67    pub system_program: Option<Program<'info, System>>,
68}
69
70impl MultisigConfig<'_> {
71    fn validate(&self) -> Result<()> {
72        require_keys_eq!(
73            self.config_authority.key(),
74            self.multisig.config_authority,
75            MultisigError::Unauthorized
76        );
77
78        Ok(())
79    }
80
81    /// Add a member/key to the multisig and reallocate space if necessary.
82    ///
83    /// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
84    ///       Uncontrolled Mustisigs should use `config_transaction_create` instead.
85    #[access_control(ctx.accounts.validate())]
86    pub fn multisig_add_member(ctx: Context<Self>, args: MultisigAddMemberArgs) -> Result<()> {
87        let MultisigAddMemberArgs { new_member, .. } = args;
88
89        let multisig = &mut ctx.accounts.multisig;
90
91        multisig.add_member(new_member);
92
93        // Make sure the multisig account can fit the newly set rent_collector.
94        Multisig::realloc_if_needed(
95            multisig.to_account_info(),
96            multisig.members.len(),
97            ctx.accounts
98                .rent_payer
99                .as_ref()
100                .map(ToAccountInfo::to_account_info),
101            ctx.accounts
102                .system_program
103                .as_ref()
104                .map(ToAccountInfo::to_account_info),
105        )?;
106
107        multisig.invalidate_prior_transactions();
108
109        multisig.invariant()?;
110
111        Ok(())
112    }
113
114    /// Remove a member/key from the multisig.
115    ///
116    /// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
117    ///       Uncontrolled Mustisigs should use `config_transaction_create` instead.
118    #[access_control(ctx.accounts.validate())]
119    pub fn multisig_remove_member(
120        ctx: Context<Self>,
121        args: MultisigRemoveMemberArgs,
122    ) -> Result<()> {
123        let multisig = &mut ctx.accounts.multisig;
124
125        require!(multisig.members.len() > 1, MultisigError::RemoveLastMember);
126
127        multisig.remove_member(args.old_member)?;
128
129        multisig.invalidate_prior_transactions();
130
131        multisig.invariant()?;
132
133        Ok(())
134    }
135
136    /// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
137    ///       Uncontrolled Mustisigs should use `config_transaction_create` instead.
138    #[access_control(ctx.accounts.validate())]
139    pub fn multisig_change_threshold(
140        ctx: Context<Self>,
141        args: MultisigChangeThresholdArgs,
142    ) -> Result<()> {
143        let MultisigChangeThresholdArgs { new_threshold, .. } = args;
144
145        let multisig = &mut ctx.accounts.multisig;
146
147        multisig.threshold = new_threshold;
148
149        multisig.invalidate_prior_transactions();
150
151        multisig.invariant()?;
152
153        Ok(())
154    }
155
156    /// Set the `time_lock` config parameter for the multisig.
157    ///
158    /// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
159    ///       Uncontrolled Mustisigs should use `config_transaction_create` instead.
160    #[access_control(ctx.accounts.validate())]
161    pub fn multisig_set_time_lock(ctx: Context<Self>, args: MultisigSetTimeLockArgs) -> Result<()> {
162        let multisig = &mut ctx.accounts.multisig;
163
164        multisig.time_lock = args.time_lock;
165
166        multisig.invalidate_prior_transactions();
167
168        multisig.invariant()?;
169
170        Ok(())
171    }
172
173    /// Set the multisig `config_authority`.
174    ///
175    /// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
176    ///       Uncontrolled Mustisigs should use `config_transaction_create` instead.
177    #[access_control(ctx.accounts.validate())]
178    pub fn multisig_set_config_authority(
179        ctx: Context<Self>,
180        args: MultisigSetConfigAuthorityArgs,
181    ) -> Result<()> {
182        let multisig = &mut ctx.accounts.multisig;
183
184        multisig.config_authority = args.config_authority;
185
186        multisig.invalidate_prior_transactions();
187
188        multisig.invariant()?;
189
190        Ok(())
191    }
192
193    /// Set the multisig `rent_collector` and reallocate space if necessary.
194    ///
195    /// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
196    ///       Uncontrolled Mustisigs should use `config_transaction_create` instead.
197    #[access_control(ctx.accounts.validate())]
198    pub fn multisig_set_rent_collector(
199        ctx: Context<Self>,
200        args: MultisigSetRentCollectorArgs,
201    ) -> Result<()> {
202        let multisig = &mut ctx.accounts.multisig;
203
204        multisig.rent_collector = args.rent_collector;
205
206        // Make sure the multisig account can fit the newly set rent_collector.
207        Multisig::realloc_if_needed(
208            multisig.to_account_info(),
209            multisig.members.len(),
210            ctx.accounts
211                .rent_payer
212                .as_ref()
213                .map(ToAccountInfo::to_account_info),
214            ctx.accounts
215                .system_program
216                .as_ref()
217                .map(ToAccountInfo::to_account_info),
218        )?;
219
220        // We don't need to invalidate prior transactions here because changing
221        // `rent_collector` doesn't affect the consensus parameters of the multisig.
222
223        multisig.invariant()?;
224
225        Ok(())
226    }
227}