Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global ProgramConfig #62

Merged
merged 1 commit into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions programs/squads_multisig_program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ default = []
[dependencies]
anchor-lang = { version = "=0.29.0", features = ["allow-missing-optionals"] }
anchor-spl = { version="=0.29.0", features=["token"] }
solana-program = "1.17.4"
solana-security-txt = "1.1.1"
#
4 changes: 4 additions & 0 deletions programs/squads_multisig_program/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub use multisig_add_spending_limit::*;
pub use multisig_config::*;
pub use multisig_create::*;
pub use multisig_remove_spending_limit::*;
pub use program_config_init::*;
pub use program_config::*;
pub use proposal_activate::*;
pub use proposal_create::*;
pub use proposal_vote::*;
Expand All @@ -24,6 +26,8 @@ mod multisig_add_spending_limit;
mod multisig_config;
mod multisig_create;
mod multisig_remove_spending_limit;
mod program_config_init;
mod program_config;
mod proposal_activate;
mod proposal_create;
mod proposal_vote;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#![allow(deprecated)]
use anchor_lang::prelude::*;
use anchor_lang::system_program;
use solana_program::native_token::LAMPORTS_PER_SOL;

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

#[derive(AnchorSerialize, AnchorDeserialize)]
Expand All @@ -20,6 +24,10 @@ pub struct MultisigCreateArgs {
pub memo: Option<String>,
}

#[deprecated(
since = "0.4.0",
note = "This instruction is deprecated and will be removed soon. Please use `multisig_create_v2` to ensure future compatibility."
)]
#[derive(Accounts)]
#[instruction(args: MultisigCreateArgs)]
pub struct MultisigCreate<'info> {
Expand All @@ -43,11 +51,84 @@ pub struct MultisigCreate<'info> {
pub system_program: Program<'info, System>,
}

#[allow(deprecated)]
impl MultisigCreate<'_> {
fn validate(&self) -> Result<()> {
Ok(())
}

/// Creates a multisig.
#[allow(deprecated)]
#[access_control(ctx.accounts.validate())]
pub fn multisig_create(ctx: Context<Self>, args: MultisigCreateArgs) -> Result<()> {
msg!("WARNING: This instruction is deprecated and will be removed soon. Please use `multisig_create_v2` to ensure future compatibility.");

// Sort the members by pubkey.
let mut members = args.members;
members.sort_by_key(|m| m.key);

// Initialize the multisig.
let multisig = &mut ctx.accounts.multisig;
multisig.config_authority = args.config_authority.unwrap_or_default();
multisig.threshold = args.threshold;
multisig.time_lock = args.time_lock;
multisig.transaction_index = 0;
multisig.stale_transaction_index = 0;
multisig.create_key = ctx.accounts.create_key.key();
multisig.bump = ctx.bumps.multisig;
multisig.members = members;
multisig.rent_collector = args.rent_collector;

multisig.invariant()?;

Ok(())
}
}

#[derive(Accounts)]
#[instruction(args: MultisigCreateArgs)]
pub struct MultisigCreateV2<'info> {
/// Global program config account.
pub program_config: Account<'info, ProgramConfig>,

/// The treasury where the creation fee is transferred to.
/// CHECK: validation is performed in the `MultisigCreate::validate()` method.
#[account(mut)]
pub treasury: AccountInfo<'info>,

#[account(
init,
payer = creator,
space = Multisig::size(args.members.len(), args.rent_collector.is_some()),
seeds = [SEED_PREFIX, SEED_MULTISIG, create_key.key().as_ref()],
bump
)]
pub multisig: Account<'info, Multisig>,

/// An ephemeral signer that is used as a seed for the Multisig PDA.
/// Must be a signer to prevent front-running attack by someone else but the original creator.
pub create_key: Signer<'info>,

/// The creator of the multisig.
#[account(mut)]
pub creator: Signer<'info>,

pub system_program: Program<'info, System>,
}

impl MultisigCreateV2<'_> {
fn validate(&self) -> Result<()> {
//region treasury
require_keys_eq!(
self.treasury.key(),
self.program_config.treasury,
MultisigError::InvalidAccount
);
//endregion

Ok(())
}

/// Creates a multisig.
#[access_control(ctx.accounts.validate())]
pub fn multisig_create(ctx: Context<Self>, args: MultisigCreateArgs) -> Result<()> {
Expand All @@ -69,6 +150,22 @@ impl MultisigCreate<'_> {

multisig.invariant()?;

let creation_fee = ctx.accounts.program_config.multisig_creation_fee;

if creation_fee > 0 {
system_program::transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.creator.to_account_info(),
to: ctx.accounts.treasury.to_account_info(),
},
),
creation_fee,
)?;
msg!("Creation fee: {}", creation_fee / LAMPORTS_PER_SOL);
}

Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use anchor_lang::prelude::*;

use crate::errors::MultisigError;

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ProgramConfigSetAuthorityArgs {
pub new_authority: Pubkey,
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ProgramConfigSetMultisigCreationFeeArgs {
pub new_multisig_creation_fee: u64,
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ProgramConfigSetTreasuryArgs {
pub new_treasury: Pubkey,
}

#[derive(Accounts)]
pub struct ProgramConfig<'info> {
#[account(mut)]
pub program_config: Account<'info, crate::state::ProgramConfig>,

pub authority: Signer<'info>,
}

impl ProgramConfig<'_> {
fn validate(&self) -> Result<()> {
let Self {
program_config,
authority,
} = self;

// authority
require_keys_eq!(
program_config.authority,
authority.key(),
MultisigError::Unauthorized
);

Ok(())
}

#[access_control(ctx.accounts.validate())]
pub fn program_config_set_authority(
ctx: Context<Self>,
args: ProgramConfigSetAuthorityArgs,
) -> Result<()> {
let program_config = &mut ctx.accounts.program_config;

program_config.authority = args.new_authority;

Ok(())
}

#[access_control(ctx.accounts.validate())]
pub fn program_config_set_multisig_creation_fee(
ctx: Context<Self>,
args: ProgramConfigSetMultisigCreationFeeArgs,
) -> Result<()> {
let program_config = &mut ctx.accounts.program_config;

program_config.multisig_creation_fee = args.new_multisig_creation_fee;

Ok(())
}

#[access_control(ctx.accounts.validate())]
pub fn program_config_set_treasury(
ctx: Context<Self>,
args: ProgramConfigSetTreasuryArgs,
) -> Result<()> {
let program_config = &mut ctx.accounts.program_config;

program_config.treasury = args.new_treasury;

Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::errors::MultisigError;
use anchor_lang::prelude::*;
use anchor_lang::solana_program::pubkey;

use crate::state::*;

/// This is a key controlled by the Squads team and is intended to use for the single
/// transaction that initializes the global program config. It is not used for anything else.
#[cfg(not(feature = "testing"))]
const INITIALIZER: Pubkey = pubkey!("HM5y4mz3Bt9JY9mr1hkyhnvqxSH4H2u2451j7Hc2dtvK");

#[cfg(feature = "testing")]
const INITIALIZER: Pubkey = pubkey!("BrQAbGdWQ9YUHmWWgKFdFe4miTURH71jkYFPXfaosqDv");

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct ProgramConfigInitArgs {
/// The authority that can configure the program config: change the treasury, etc.
pub authority: Pubkey,
/// The fee that is charged for creating a new multisig.
pub multisig_creation_fee: u64,
/// The treasury where the creation fee is transferred to.
pub treasury: Pubkey,
}

#[derive(Accounts)]
pub struct ProgramConfigInit<'info> {
#[account(
init,
payer = initializer,
space = 8 + ProgramConfig::INIT_SPACE,
seeds = [SEED_PREFIX, SEED_PROGRAM_CONFIG],
bump
)]
pub program_config: Account<'info, ProgramConfig>,

/// The hard-coded account that is used to initialize the program config once.
#[account(
mut,
address = INITIALIZER @ MultisigError::Unauthorized
)]
pub initializer: Signer<'info>,

pub system_program: Program<'info, System>,
}

impl ProgramConfigInit<'_> {
/// A one-time instruction that initializes the global program config.
pub fn program_config_init(ctx: Context<Self>, args: ProgramConfigInitArgs) -> Result<()> {
let program_config = &mut ctx.accounts.program_config;

program_config.authority = args.authority;
program_config.multisig_creation_fee = args.multisig_creation_fee;
program_config.treasury = args.treasury;

program_config.invariant()?;

Ok(())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ impl ConfigTransactionAccountsClose<'_> {
// Has to be either stale or in a terminal state.
let is_stale = proposal.transaction_index <= multisig.stale_transaction_index;

#[allow(deprecated)]
let can_close = match proposal.status {
// Draft proposals can only be closed if stale,
// so they can't be activated anymore.
Expand Down Expand Up @@ -188,6 +189,7 @@ impl VaultTransactionAccountsClose<'_> {

let is_stale = proposal.transaction_index <= multisig.stale_transaction_index;

#[allow(deprecated)]
let can_close = match proposal.status {
// Draft proposals can only be closed if stale,
// so they can't be activated anymore.
Expand Down Expand Up @@ -351,6 +353,7 @@ impl VaultBatchTransactionAccountClose<'_> {

// Batch transactions that are marked as executed within the batch can be closed,
// otherwise we need to check the proposal status.
#[allow(deprecated)]
let can_close = is_batch_transaction_executed
|| match proposal.status {
// Transactions of Draft proposals can only be closed if stale,
Expand Down Expand Up @@ -467,6 +470,7 @@ impl BatchAccountsClose<'_> {

let is_stale = proposal.transaction_index <= multisig.stale_transaction_index;

#[allow(deprecated)]
let can_close = match proposal.status {
// Draft proposals can only be closed if stale,
// so they can't be activated anymore.
Expand Down
Loading