Skip to content

Commit

Permalink
feat(SpendingLimits): add/remove spending limit for controlled multisig
Browse files Browse the repository at this point in the history
  • Loading branch information
vovacodes committed Aug 25, 2023
1 parent e4ce513 commit ba0f95c
Show file tree
Hide file tree
Showing 19 changed files with 895 additions and 21 deletions.
4 changes: 4 additions & 0 deletions programs/multisig/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ pub use batch_create::*;
pub use batch_execute_transaction::*;
pub use config_transaction_create::*;
pub use config_transaction_execute::*;
pub use multisig_add_spending_limit::*;
pub use multisig_config::*;
pub use multisig_create::*;
pub use multisig_remove_spending_limit::*;
pub use proposal_activate::*;
pub use proposal_create::*;
pub use proposal_vote::*;
Expand All @@ -17,8 +19,10 @@ mod batch_create;
mod batch_execute_transaction;
mod config_transaction_create;
mod config_transaction_execute;
mod multisig_add_spending_limit;
mod multisig_config;
mod multisig_create;
mod multisig_remove_spending_limit;
mod proposal_activate;
mod proposal_create;
mod proposal_vote;
Expand Down
103 changes: 103 additions & 0 deletions programs/multisig/src/instructions/multisig_add_spending_limit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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 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>,
/// 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 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;

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.get("spending_limit").unwrap();
spending_limit.members = args.members;
spending_limit.destinations = args.destinations;

Ok(())
}
}
12 changes: 0 additions & 12 deletions programs/multisig/src/instructions/multisig_config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Mint;

use crate::errors::*;
use crate::state::*;
Expand Down Expand Up @@ -200,15 +199,4 @@ impl MultisigConfig<'_> {

Ok(())
}

// /// Create a new spending limit for a vault.
// /// 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(&args))]
// pub fn multisig_add_spending_limit(
// ctx: Context<Self>,
// args: MultisigAddSpendingLimitArgs,
// ) -> Result<()> {
// todo!()
// }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use anchor_lang::prelude::*;

use crate::errors::*;
use crate::state::*;

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct MultisigRemoveSpendingLimitArgs {
/// Memo is used for indexing only.
pub memo: Option<String>,
}

#[derive(Accounts)]
pub struct MultisigRemoveSpendingLimit<'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(mut, close = rent_collector)]
pub spending_limit: Account<'info, SpendingLimit>,

/// This is usually the same as `config_authority`, but can be a different account if needed.
/// CHECK: can be any account.
#[account(mut)]
pub rent_collector: AccountInfo<'info>,
}

impl MultisigRemoveSpendingLimit<'_> {
fn validate(&self) -> Result<()> {
// config_authority
require_keys_eq!(
self.config_authority.key(),
self.multisig.config_authority,
MultisigError::Unauthorized
);

// `spending_limit`
require_keys_eq!(
self.spending_limit.multisig,
self.multisig.key(),
MultisigError::InvalidAccount
);

Ok(())
}

/// Remove the spending limit from 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_remove_spending_limit(
ctx: Context<Self>,
_args: MultisigRemoveSpendingLimitArgs,
) -> Result<()> {
Ok(())
}
}
22 changes: 19 additions & 3 deletions programs/multisig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,23 @@ pub mod multisig {
MultisigCreate::multisig_create(ctx, args)
}

/// Add a new member to the multisig.
/// Add a new member to the controlled multisig.
pub fn multisig_add_member(
ctx: Context<MultisigConfig>,
args: MultisigAddMemberArgs,
) -> Result<()> {
MultisigConfig::multisig_add_member(ctx, args)
}

/// Remove a member/key from the multisig.
/// Remove a member/key from the controlled multisig.
pub fn multisig_remove_member(
ctx: Context<MultisigConfig>,
args: MultisigRemoveMemberArgs,
) -> Result<()> {
MultisigConfig::multisig_remove_member(ctx, args)
}

/// Set the `time_lock` config parameter for the multisig.
/// Set the `time_lock` config parameter for the controlled multisig.
pub fn multisig_set_time_lock(
ctx: Context<MultisigConfig>,
args: MultisigSetTimeLockArgs,
Expand All @@ -60,6 +60,22 @@ pub mod multisig {
MultisigConfig::multisig_set_config_authority(ctx, args)
}

/// Create a new spending limit for the controlled multisig.
pub fn multisig_add_spending_limit(
ctx: Context<MultisigAddSpendingLimit>,
args: MultisigAddSpendingLimitArgs,
) -> Result<()> {
MultisigAddSpendingLimit::multisig_add_spending_limit(ctx, args)
}

/// Remove the spending limit from the controlled multisig.
pub fn multisig_remove_spending_limit(
ctx: Context<MultisigRemoveSpendingLimit>,
args: MultisigRemoveSpendingLimitArgs,
) -> Result<()> {
MultisigRemoveSpendingLimit::multisig_remove_spending_limit(ctx, args)
}

/// Create a new config transaction.
pub fn config_transaction_create(
ctx: Context<ConfigTransactionCreate>,
Expand Down
Loading

0 comments on commit ba0f95c

Please sign in to comment.