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
102
103
104
105
106
107
108
use anchor_lang::prelude::*;
use crate::errors::*;
use crate::state::*;
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct MultisigAddSpendingLimitArgs {
/// 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.
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,
/// Members of the Spending Limit that can use it.
/// Don't have to be members of the multisig.
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>,
/// Memo is used for indexing only.
pub memo: Option<String>,
}
#[derive(Accounts)]
#[instruction(args: MultisigAddSpendingLimitArgs)]
pub struct MultisigAddSpendingLimit<'info> {
#[account(
seeds = [SEED_PREFIX, SEED_MULTISIG, multisig.create_key.as_ref()],
bump = multisig.bump,
)]
multisig: Account<'info, Multisig>,
/// Multisig `config_authority` that must authorize the configuration change.
pub config_authority: Signer<'info>,
#[account(
init,
seeds = [
SEED_PREFIX,
multisig.key().as_ref(),
SEED_SPENDING_LIMIT,
args.create_key.as_ref(),
],
bump,
space = SpendingLimit::size(args.members.len(), args.destinations.len()),
payer = rent_payer
)]
pub spending_limit: Account<'info, SpendingLimit>,
/// This is usually the same as `config_authority`, but can be a different account if needed.
#[account(mut)]
pub rent_payer: Signer<'info>,
pub system_program: Program<'info, System>,
}
impl MultisigAddSpendingLimit<'_> {
fn validate(&self) -> Result<()> {
// config_authority
require_keys_eq!(
self.config_authority.key(),
self.multisig.config_authority,
MultisigError::Unauthorized
);
// `spending_limit` is partially checked via its seeds.
Ok(())
}
/// Create a new spending limit for the controlled multisig.
/// NOTE: This instruction must be called only by the `config_authority` if one is set (Controlled Multisig).
/// Uncontrolled Mustisigs should use `config_transaction_create` instead.
#[access_control(ctx.accounts.validate())]
pub fn multisig_add_spending_limit(
ctx: Context<Self>,
args: MultisigAddSpendingLimitArgs,
) -> Result<()> {
let spending_limit = &mut ctx.accounts.spending_limit;
// Make sure there are no duplicate keys in this direct invocation by sorting so the invariant will catch
let mut sorted_members = args.members;
sorted_members.sort();
spending_limit.multisig = ctx.accounts.multisig.key();
spending_limit.create_key = args.create_key;
spending_limit.vault_index = args.vault_index;
spending_limit.mint = args.mint;
spending_limit.amount = args.amount;
spending_limit.period = args.period;
spending_limit.remaining_amount = args.amount;
spending_limit.last_reset = Clock::get()?.unix_timestamp;
spending_limit.bump = ctx.bumps.spending_limit;
spending_limit.members = sorted_members;
spending_limit.destinations = args.destinations;
spending_limit.invariant()?;
Ok(())
}
}