Skip to main content

squads_multisig_program/instructions/
multisig_add_spending_limit.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4use crate::state::*;
5
6#[derive(AnchorSerialize, AnchorDeserialize)]
7pub struct MultisigAddSpendingLimitArgs {
8    /// Key that is used to seed the SpendingLimit PDA.
9    pub create_key: Pubkey,
10    /// The index of the vault that the spending limit is for.
11    pub vault_index: u8,
12    /// The token mint the spending limit is for.
13    pub mint: Pubkey,
14    /// The amount of tokens that can be spent in a period.
15    /// This amount is in decimals of the mint,
16    /// so 1 SOL would be `1_000_000_000` and 1 USDC would be `1_000_000`.
17    pub amount: u64,
18    /// The reset period of the spending limit.
19    /// When it passes, the remaining amount is reset, unless it's `Period::OneTime`.
20    pub period: Period,
21    /// Members of the Spending Limit that can use it.
22    /// Don't have to be members of the multisig.
23    pub members: Vec<Pubkey>,
24    /// The destination addresses the spending limit is allowed to sent funds to.
25    /// If empty, funds can be sent to any address.
26    pub destinations: Vec<Pubkey>,
27    /// Memo is used for indexing only.
28    pub memo: Option<String>,
29}
30
31#[derive(Accounts)]
32#[instruction(args: MultisigAddSpendingLimitArgs)]
33pub struct MultisigAddSpendingLimit<'info> {
34    #[account(
35        seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
36        bump = multisig.bump,
37    )]
38    multisig: Account<'info, Multisig>,
39
40    /// Multisig `config_authority` that must authorize the configuration change.
41    pub config_authority: Signer<'info>,
42
43    #[account(
44        init,
45        seeds = [
46            SEED_PREFIX,
47            multisig.key().as_ref(),
48            SEED_SPENDING_LIMIT,
49            args.create_key.as_ref(),
50        ],
51        bump,
52        space = SpendingLimit::size(args.members.len(), args.destinations.len()),
53        payer = rent_payer
54    )]
55    pub spending_limit: Account<'info, SpendingLimit>,
56
57    /// This is usually the same as `config_authority`, but can be a different account if needed.
58    #[account(mut)]
59    pub rent_payer: Signer<'info>,
60
61    pub system_program: Program<'info, System>,
62}
63
64impl MultisigAddSpendingLimit<'_> {
65    fn validate(&self) -> Result<()> {
66        // config_authority
67        require_keys_eq!(
68            self.config_authority.key(),
69            self.multisig.config_authority,
70            MultisigError::Unauthorized
71        );
72
73        // `spending_limit` is partially checked via its seeds.
74
75        Ok(())
76    }
77
78    /// Create a new spending limit for the controlled multisig.
79    /// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
80    ///       Uncontrolled Mustisigs should use `config_transaction_create` instead.
81    #[access_control(ctx.accounts.validate())]
82    pub fn multisig_add_spending_limit(
83        ctx: Context<Self>,
84        args: MultisigAddSpendingLimitArgs,
85    ) -> Result<()> {
86        let spending_limit = &mut ctx.accounts.spending_limit;
87
88        // Make sure there are no duplicate keys in this direct invocation by sorting so the invariant will catch
89        let mut sorted_members = args.members;
90        sorted_members.sort();
91
92        spending_limit.multisig = ctx.accounts.multisig.key();
93        spending_limit.create_key = args.create_key;
94        spending_limit.vault_index = args.vault_index;
95        spending_limit.mint = args.mint;
96        spending_limit.amount = args.amount;
97        spending_limit.period = args.period;
98        spending_limit.remaining_amount = args.amount;
99        spending_limit.last_reset = Clock::get()?.unix_timestamp;
100        spending_limit.bump = ctx.bumps.spending_limit;
101        spending_limit.members = sorted_members;
102        spending_limit.destinations = args.destinations;
103
104        spending_limit.invariant()?;
105
106        Ok(())
107    }
108}