Skip to main content

squads_multisig_program/instructions/
config_transaction_execute.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::id;
5use crate::state::*;
6use crate::utils::*;
7
8#[derive(Accounts)]
9pub struct ConfigTransactionExecute<'info> {
10    /// The multisig account that owns the transaction.
11    #[account(
12        mut,
13        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
14        bump = multisig.bump,
15    )]
16    pub multisig: Box<Account<'info, Multisig>>,
17
18    /// One of the multisig members with `Execute` permission.
19    pub member: Signer<'info>,
20
21    /// The proposal account associated with the transaction.
22    #[account(
23        mut,
24        seeds = [
25            SEED_PREFIX,
26            multisig.key().as_ref(),
27            SEED_TRANSACTION,
28            &transaction.index.to_le_bytes(),
29            SEED_PROPOSAL,
30        ],
31        bump = proposal.bump,
32    )]
33    pub proposal: Account<'info, Proposal>,
34
35    /// The transaction to execute.
36    #[account(
37        seeds = [
38            SEED_PREFIX,
39            multisig.key().as_ref(),
40            SEED_TRANSACTION,
41            &transaction.index.to_le_bytes(),
42        ],
43        bump = transaction.bump,
44    )]
45    pub transaction: Account<'info, ConfigTransaction>,
46
47    /// The account that will be charged/credited in case the config transaction causes space reallocation,
48    /// for example when adding a new member, adding or removing a spending limit.
49    /// This is usually the same as `member`, but can be a different account if needed.
50    #[account(mut)]
51    pub rent_payer: Option<Signer<'info>>,
52
53    /// We might need it in case reallocation is needed.
54    pub system_program: Option<Program<'info, System>>,
55    // In case the transaction contains Add(Remove)SpendingLimit actions,
56    // `remaining_accounts` must contain the SpendingLimit accounts to be initialized/closed.
57    // remaining_accounts
58}
59
60impl<'info> ConfigTransactionExecute<'info> {
61    fn validate(&self) -> Result<()> {
62        let Self {
63            multisig,
64            proposal,
65            member,
66            ..
67        } = self;
68
69        // member
70        require!(
71            multisig.is_member(member.key()).is_some(),
72            MultisigError::NotAMember
73        );
74        require!(
75            multisig.member_has_permission(member.key(), Permission::Execute),
76            MultisigError::Unauthorized
77        );
78
79        // proposal
80        match proposal.status {
81            ProposalStatus::Approved { timestamp } => {
82                require!(
83                    Clock::get()?.unix_timestamp - timestamp >= i64::from(multisig.time_lock),
84                    MultisigError::TimeLockNotReleased
85                );
86            }
87            _ => return err!(MultisigError::InvalidProposalStatus),
88        }
89        // Stale config transaction proposals CANNOT be executed even if approved.
90        require!(
91            proposal.transaction_index > multisig.stale_transaction_index,
92            MultisigError::StaleProposal
93        );
94
95        // `transaction` is validated by its seeds.
96
97        Ok(())
98    }
99
100    /// Execute the multisig transaction.
101    /// The transaction must be `Approved`.
102    #[access_control(ctx.accounts.validate())]
103    pub fn config_transaction_execute(ctx: Context<'_, '_, 'info, 'info, Self>) -> Result<()> {
104        let multisig = &mut ctx.accounts.multisig;
105        let transaction = &ctx.accounts.transaction;
106        let proposal = &mut ctx.accounts.proposal;
107
108        let rent = Rent::get()?;
109
110        // Execute the actions one by one.
111        for action in transaction.actions.iter() {
112            match action {
113                ConfigAction::AddMember { new_member } => {
114                    multisig.add_member(new_member.to_owned());
115
116                    multisig.invalidate_prior_transactions();
117                }
118
119                ConfigAction::RemoveMember { old_member } => {
120                    multisig.remove_member(old_member.to_owned())?;
121
122                    multisig.invalidate_prior_transactions();
123                }
124
125                ConfigAction::ChangeThreshold { new_threshold } => {
126                    multisig.threshold = *new_threshold;
127
128                    multisig.invalidate_prior_transactions();
129                }
130
131                ConfigAction::SetTimeLock { new_time_lock } => {
132                    multisig.time_lock = *new_time_lock;
133
134                    multisig.invalidate_prior_transactions();
135                }
136
137                ConfigAction::AddSpendingLimit {
138                    create_key,
139                    vault_index,
140                    mint,
141                    amount,
142                    period,
143                    members,
144                    destinations,
145                } => {
146                    let (spending_limit_key, spending_limit_bump) = Pubkey::find_program_address(
147                        &[
148                            SEED_PREFIX,
149                            multisig.key().as_ref(),
150                            SEED_SPENDING_LIMIT,
151                            create_key.as_ref(),
152                        ],
153                        ctx.program_id,
154                    );
155
156                    // Find the SpendingLimit account in `remaining_accounts`.
157                    let spending_limit_info = ctx
158                        .remaining_accounts
159                        .iter()
160                        .find(|acc| acc.key == &spending_limit_key)
161                        .ok_or(MultisigError::MissingAccount)?;
162
163                    // `rent_payer` and `system_program` must also be present.
164                    let rent_payer = &ctx
165                        .accounts
166                        .rent_payer
167                        .as_ref()
168                        .ok_or(MultisigError::MissingAccount)?;
169                    let system_program = &ctx
170                        .accounts
171                        .system_program
172                        .as_ref()
173                        .ok_or(MultisigError::MissingAccount)?;
174
175                    // Initialize the SpendingLimit account.
176                    create_account(
177                        rent_payer,
178                        spending_limit_info,
179                        system_program,
180                        &id(),
181                        &rent,
182                        SpendingLimit::size(members.len(), destinations.len()),
183                        vec![
184                            SEED_PREFIX.to_vec(),
185                            multisig.key().as_ref().to_vec(),
186                            SEED_SPENDING_LIMIT.to_vec(),
187                            create_key.as_ref().to_vec(),
188                            vec![spending_limit_bump],
189                        ],
190                    )?;
191
192                    let mut members = members.to_vec();
193                    // Make sure members are sorted.
194                    members.sort();
195
196                    // Serialize the SpendingLimit data into the account info.
197                    let spending_limit = SpendingLimit {
198                        multisig: multisig.key().to_owned(),
199                        create_key: create_key.to_owned(),
200                        vault_index: *vault_index,
201                        amount: *amount,
202                        mint: *mint,
203                        period: *period,
204                        remaining_amount: *amount,
205                        last_reset: Clock::get()?.unix_timestamp,
206                        bump: spending_limit_bump,
207                        members,
208                        destinations: destinations.to_vec(),
209                    };
210
211                    spending_limit.invariant()?;
212
213                    spending_limit
214                        .try_serialize(&mut &mut spending_limit_info.data.borrow_mut()[..])?;
215                }
216
217                ConfigAction::RemoveSpendingLimit {
218                    spending_limit: spending_limit_key,
219                } => {
220                    // Find the SpendingLimit account in `remaining_accounts`.
221                    let spending_limit_info = ctx
222                        .remaining_accounts
223                        .iter()
224                        .find(|acc| acc.key == spending_limit_key)
225                        .ok_or(MultisigError::MissingAccount)?;
226
227                    // `rent_payer` must also be present.
228                    let rent_payer = &ctx
229                        .accounts
230                        .rent_payer
231                        .as_ref()
232                        .ok_or(MultisigError::MissingAccount)?;
233
234                    let spending_limit = Account::<SpendingLimit>::try_from(spending_limit_info)?;
235
236                    // SpendingLimit must belong to the `multisig`.
237                    require_keys_eq!(
238                        spending_limit.multisig,
239                        multisig.key(),
240                        MultisigError::InvalidAccount
241                    );
242
243                    spending_limit.close(rent_payer.to_account_info())?;
244
245                    // We don't need to invalidate prior transactions here because adding
246                    // a spending limit doesn't affect the consensus parameters of the multisig.
247                }
248
249                ConfigAction::SetRentCollector { new_rent_collector } => {
250                    multisig.rent_collector = *new_rent_collector;
251
252                    // We don't need to invalidate prior transactions here because changing
253                    // `rent_collector` doesn't affect the consensus parameters of the multisig.
254                }
255            }
256        }
257
258        // Make sure the multisig account can fit the updated state: added members or newly set rent_collector.
259        Multisig::realloc_if_needed(
260            multisig.to_account_info(),
261            multisig.members.len(),
262            ctx.accounts
263                .rent_payer
264                .as_ref()
265                .map(ToAccountInfo::to_account_info),
266            ctx.accounts
267                .system_program
268                .as_ref()
269                .map(ToAccountInfo::to_account_info),
270        )?;
271
272        // Make sure the multisig state is valid after applying the actions.
273        multisig.invariant()?;
274
275        // Mark the proposal as executed.
276        proposal.status = ProposalStatus::Executed {
277            timestamp: Clock::get()?.unix_timestamp,
278        };
279
280        Ok(())
281    }
282}