1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use anchor_lang::prelude::*;

use crate::errors::*;

#[account]
pub struct SpendingLimit {
    /// The multisig this belongs to.
    pub multisig: Pubkey,

    /// Key that is used to seed the SpendingLimit PDA.
    pub create_key: Pubkey,

    /// The index of the vault that the spending limit is for.
    pub vault_index: u8,

    /// The token mint the spending limit is for.
    /// Pubkey::default() means SOL.
    /// use NATIVE_MINT for Wrapped SOL.
    pub mint: Pubkey,

    /// The amount of tokens that can be spent in a period.
    /// This amount is in decimals of the mint,
    /// so 1 SOL would be `1_000_000_000` and 1 USDC would be `1_000_000`.
    pub amount: u64,

    /// The reset period of the spending limit.
    /// When it passes, the remaining amount is reset, unless it's `Period::OneTime`.
    pub period: Period,

    /// The remaining amount of tokens that can be spent in the current period.
    /// When reaches 0, the spending limit cannot be used anymore until the period reset.
    pub remaining_amount: u64,

    /// Unix timestamp marking the last time the spending limit was reset (or created).
    pub last_reset: i64,

    /// PDA bump.
    pub bump: u8,

    /// Members of the multisig that can use the spending limit.
    /// In case a member is removed from the multisig, the spending limit will remain existent
    /// (until explicitly deleted), but the removed member will not be able to use it anymore.
    pub members: Vec<Pubkey>,

    /// The destination addresses the spending limit is allowed to sent funds to.
    /// If empty, funds can be sent to any address.
    pub destinations: Vec<Pubkey>,
}

impl SpendingLimit {
    pub fn size(members_length: usize, destinations_length: usize) -> usize {
        8  + // anchor discriminator
        32 + // multisig
        32 + // create_key
        1  + // vault_index
        32 + // mint
        8  + // amount
        1  + // period
        8  + // remaining_amount
        8  + // last_reset
        1  + // bump
        4  + // members vector length
        members_length * 32 + // members
        4  + // destinations vector length
        destinations_length * 32 // destinations
    }

    pub fn invariant(&self) -> Result<()> {
        require!(!self.members.is_empty(), MultisigError::EmptyMembers);

        // There must be no duplicate members, we make sure members are sorted when creating a SpendingLimit.
        let has_duplicates = self.members.windows(2).any(|win| win[0] == win[1]);
        require!(!has_duplicates, MultisigError::DuplicateMember);

        Ok(())
    }
}

/// The reset period of the spending limit.
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Period {
    /// The spending limit can only be used once.
    OneTime,
    /// The spending limit is reset every day.
    Day,
    /// The spending limit is reset every week (7 days).
    Week,
    /// The spending limit is reset every month (30 days).
    Month,
}

impl Period {
    pub fn to_seconds(&self) -> Option<i64> {
        match self {
            Period::OneTime => None,
            Period::Day => Some(24 * 60 * 60),
            Period::Week => Some(7 * 24 * 60 * 60),
            Period::Month => Some(30 * 24 * 60 * 60),
        }
    }
}