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),
}
}
}