squads_multisig_program/state/
spending_limit.rs

1use anchor_lang::prelude::*;
2
3use crate::errors::*;
4
5#[account]
6pub struct SpendingLimit {
7    /// The multisig this belongs to.
8    pub multisig: Pubkey,
9
10    /// Key that is used to seed the SpendingLimit PDA.
11    pub create_key: Pubkey,
12
13    /// The index of the vault that the spending limit is for.
14    pub vault_index: u8,
15
16    /// The token mint the spending limit is for.
17    /// Pubkey::default() means SOL.
18    /// use NATIVE_MINT for Wrapped SOL.
19    pub mint: Pubkey,
20
21    /// The amount of tokens that can be spent in a period.
22    /// This amount is in decimals of the mint,
23    /// so 1 SOL would be `1_000_000_000` and 1 USDC would be `1_000_000`.
24    pub amount: u64,
25
26    /// The reset period of the spending limit.
27    /// When it passes, the remaining amount is reset, unless it's `Period::OneTime`.
28    pub period: Period,
29
30    /// The remaining amount of tokens that can be spent in the current period.
31    /// When reaches 0, the spending limit cannot be used anymore until the period reset.
32    pub remaining_amount: u64,
33
34    /// Unix timestamp marking the last time the spending limit was reset (or created).
35    pub last_reset: i64,
36
37    /// PDA bump.
38    pub bump: u8,
39
40    /// Members of the multisig that can use the spending limit.
41    /// In case a member is removed from the multisig, the spending limit will remain existent
42    /// (until explicitly deleted), but the removed member will not be able to use it anymore.
43    pub members: Vec<Pubkey>,
44
45    /// The destination addresses the spending limit is allowed to sent funds to.
46    /// If empty, funds can be sent to any address.
47    pub destinations: Vec<Pubkey>,
48}
49
50impl SpendingLimit {
51    pub fn size(members_length: usize, destinations_length: usize) -> usize {
52        8  + // anchor discriminator
53        32 + // multisig
54        32 + // create_key
55        1  + // vault_index
56        32 + // mint
57        8  + // amount
58        1  + // period
59        8  + // remaining_amount
60        8  + // last_reset
61        1  + // bump
62        4  + // members vector length
63        members_length * 32 + // members
64        4  + // destinations vector length
65        destinations_length * 32 // destinations
66    }
67
68    pub fn invariant(&self) -> Result<()> {
69        // Amount must be positive.
70        require_neq!(self.amount, 0, MultisigError::SpendingLimitInvalidAmount);
71
72        require!(!self.members.is_empty(), MultisigError::EmptyMembers);
73
74        // There must be no duplicate members, we make sure members are sorted when creating a SpendingLimit.
75        let has_duplicates = self.members.windows(2).any(|win| win[0] == win[1]);
76        require!(!has_duplicates, MultisigError::DuplicateMember);
77
78        Ok(())
79    }
80}
81
82/// The reset period of the spending limit.
83#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, PartialEq, Eq)]
84pub enum Period {
85    /// The spending limit can only be used once.
86    OneTime,
87    /// The spending limit is reset every day.
88    Day,
89    /// The spending limit is reset every week (7 days).
90    Week,
91    /// The spending limit is reset every month (30 days).
92    Month,
93}
94
95impl Period {
96    pub fn to_seconds(&self) -> Option<i64> {
97        match self {
98            Period::OneTime => None,
99            Period::Day => Some(24 * 60 * 60),
100            Period::Week => Some(7 * 24 * 60 * 60),
101            Period::Month => Some(30 * 24 * 60 * 60),
102        }
103    }
104}