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}