Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use super::*;

mod admin {
use anchor_lang::prelude::declare_id;

// TODO - update to new admin key
declare_id!("CWGawadYU8CzRVBecnJymNw97H7E3ndDinV5sMzesgY2");
}

#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
pub struct AdminEnqueueMultisigProposalApprovalArgs {
pub transaction_index: u64,
}

#[derive(Accounts)]
#[instruction(args: AdminEnqueueMultisigProposalApprovalArgs)]
pub struct AdminEnqueueMultisigProposalApproval<'info> {
#[account(has_one = squads_multisig)]
pub dao: Account<'info, Dao>,

#[account(mut)]
pub admin: Signer<'info>,

#[account(
seeds = [
squads_multisig_program::SEED_PREFIX,
squads_multisig_program::SEED_MULTISIG,
dao.key().as_ref(),
],
bump,
seeds::program = squads_multisig_program::ID,
)]
pub squads_multisig: Account<'info, squads_multisig_program::Multisig>,

#[account(
seeds = [
squads_multisig_program::SEED_PREFIX,
squads_multisig.key().as_ref(),
squads_multisig_program::SEED_TRANSACTION,
args.transaction_index.to_le_bytes().as_ref(),
squads_multisig_program::SEED_PROPOSAL,
],
bump,
seeds::program = squads_multisig_program::ID,
)]
pub squads_multisig_proposal: Account<'info, squads_multisig_program::Proposal>,

#[account(
init,
payer = admin,
space = 8 + EnqueuedMultisigProposalApproval::INIT_SPACE,
seeds = [
SEED_ENQUEUED_MULTISIG_PROPOSAL_APPROVAL,
dao.key().as_ref(),
args.transaction_index.to_le_bytes().as_ref(),
],
bump,
)]
pub enqueued_approval: Account<'info, EnqueuedMultisigProposalApproval>,

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

impl AdminEnqueueMultisigProposalApproval<'_> {
pub fn validate(&self, _args: &AdminEnqueueMultisigProposalApprovalArgs) -> Result<()> {
#[cfg(feature = "production")]
require_keys_eq!(self.admin.key(), admin::ID, FutarchyError::InvalidAdmin);

if !matches!(self.dao.amm.state, PoolState::Spot { .. }) {
return Err(FutarchyError::PoolNotInSpotState.into());
}

if self.dao.optimistic_proposal.is_some() {
return Err(FutarchyError::ActiveOptimisticProposalAlreadyEnqueued.into());
}

validate_squads_proposal(
&self.squads_multisig_proposal,
&self.squads_multisig,
&self.dao.squads_multisig,
&self.dao.key(),
)?;

Ok(())
}

pub fn handle(
ctx: Context<Self>,
args: AdminEnqueueMultisigProposalApprovalArgs,
) -> Result<()> {
let enqueued = &mut ctx.accounts.enqueued_approval;

enqueued.dao = ctx.accounts.dao.key();
enqueued.transaction_index = args.transaction_index;
enqueued.pda_bump = ctx.bumps.enqueued_approval;

Ok(())
}
}

pub fn validate_squads_proposal(
squads_proposal: &squads_multisig_program::Proposal,
squads_multisig: &squads_multisig_program::Multisig,
dao_multisig_key: &Pubkey,
dao_key: &Pubkey,
) -> Result<()> {
require_keys_eq!(squads_proposal.multisig, *dao_multisig_key);

match squads_proposal.status {
squads_multisig_program::ProposalStatus::Active { timestamp: _ } => {}
_ => {
msg!("squads proposal status: {:?}", squads_proposal.status);
return Err(FutarchyError::InvalidSquadsProposalStatus.into());
}
}

require_gt!(
squads_proposal.transaction_index,
squads_multisig.stale_transaction_index
);

require!(
!squads_proposal.approved.contains(dao_key),
FutarchyError::InvalidSquadsProposalStatus
);

Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
use super::*;

mod admin {
use anchor_lang::prelude::declare_id;

// MetaDAO-controlled admin - cannot be a Squads signer because of reentrancy
declare_id!("CWGawadYU8CzRVBecnJymNw97H7E3ndDinV5sMzesgY2");
}

#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)]
pub struct AdminApproveMultisigProposalArgs {
pub transaction_index: u64,
}

#[derive(Accounts)]
#[instruction(args: AdminApproveMultisigProposalArgs)]
pub struct AdminApproveMultisigProposal<'info> {
pub struct ExecuteMultisigProposalApproval<'info> {
#[account(mut, has_one = squads_multisig)]
pub dao: Account<'info, Dao>,

#[account(mut)]
pub admin: Signer<'info>,
pub rent_receiver: Signer<'info>,

#[account(
mut,
Expand All @@ -28,7 +16,7 @@ pub struct AdminApproveMultisigProposal<'info> {
dao.key().as_ref(),
],
bump,
seeds::program = squads_multisig_program
seeds::program = squads_multisig_program::ID,
)]
pub squads_multisig: Account<'info, squads_multisig_program::Multisig>,

Expand All @@ -38,41 +26,58 @@ pub struct AdminApproveMultisigProposal<'info> {
squads_multisig_program::SEED_PREFIX,
squads_multisig.key().as_ref(),
squads_multisig_program::SEED_TRANSACTION,
args.transaction_index.to_le_bytes().as_ref(),
enqueued_approval.transaction_index.to_le_bytes().as_ref(),
squads_multisig_program::SEED_PROPOSAL,
],
bump,
seeds::program = squads_multisig_program
seeds::program = squads_multisig_program::ID,
)]
pub squads_multisig_proposal: Account<'info, squads_multisig_program::Proposal>,

#[account(
mut,
close = rent_receiver,
has_one = dao,
seeds = [
SEED_ENQUEUED_MULTISIG_PROPOSAL_APPROVAL,
dao.key().as_ref(),
enqueued_approval.transaction_index.to_le_bytes().as_ref(),
],
bump = enqueued_approval.pda_bump,
)]
pub enqueued_approval: Account<'info, EnqueuedMultisigProposalApproval>,

pub squads_multisig_program:
Program<'info, squads_multisig_program::program::SquadsMultisigProgram>,
}

impl AdminApproveMultisigProposal<'_> {
pub fn validate(&self, _args: &AdminApproveMultisigProposalArgs) -> Result<()> {
#[cfg(feature = "production")]
require_keys_eq!(self.admin.key(), admin::ID, FutarchyError::InvalidAdmin);

impl ExecuteMultisigProposalApproval<'_> {
pub fn validate(&self) -> Result<()> {
if !matches!(self.dao.amm.state, PoolState::Spot { .. }) {
return Err(FutarchyError::PoolNotInSpotState.into());
}

require!(
self.dao.optimistic_proposal.is_none(),
FutarchyError::ActiveOptimisticProposalAlreadyEnqueued
);
if self.dao.optimistic_proposal.is_some() {
return Err(FutarchyError::ActiveOptimisticProposalAlreadyEnqueued.into());
}

validate_squads_proposal(
&self.squads_multisig_proposal,
&self.squads_multisig,
&self.dao.squads_multisig,
&self.dao.key(),
)?;

Ok(())
}

pub fn handle(ctx: Context<Self>, _args: AdminApproveMultisigProposalArgs) -> Result<()> {
pub fn handle(ctx: Context<Self>) -> Result<()> {
let Self {
dao,
admin: _,
rent_receiver: _,
squads_multisig,
squads_multisig_proposal,
enqueued_approval: _,
squads_multisig_program,
} = ctx.accounts;

Expand All @@ -81,7 +86,6 @@ impl AdminApproveMultisigProposal<'_> {
let dao_seeds = &[SEED_DAO, dao_creator_key, dao_nonce, &[dao.pda_bump]];
let dao_signer = &[&dao_seeds[..]];

// Approve the proposal
squads_multisig_program::cpi::proposal_approve(
CpiContext::new_with_signer(
squads_multisig_program.to_account_info(),
Expand Down
6 changes: 4 additions & 2 deletions programs/futarchy/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::*;

pub mod admin_approve_multisig_proposal;
pub mod admin_cancel_proposal;
pub mod admin_enqueue_multisig_proposal_approval;
pub mod admin_execute_multisig_proposal;
pub mod admin_remove_proposal;
pub mod collect_fees;
pub mod collect_meteora_damm_fees;
pub mod conditional_swap;
pub mod execute_multisig_proposal_approval;
pub mod execute_spending_limit_change;
pub mod finalize_optimistic_proposal;
pub mod finalize_proposal;
Expand All @@ -23,13 +24,14 @@ pub mod unstake_from_proposal;
pub mod update_dao;
pub mod withdraw_liquidity;

pub use admin_approve_multisig_proposal::*;
pub use admin_cancel_proposal::*;
pub use admin_enqueue_multisig_proposal_approval::*;
pub use admin_execute_multisig_proposal::*;
pub use admin_remove_proposal::*;
pub use collect_fees::*;
pub use collect_meteora_damm_fees::*;
pub use conditional_swap::*;
pub use execute_multisig_proposal_approval::*;
pub use execute_spending_limit_change::*;
pub use finalize_optimistic_proposal::*;
pub use finalize_proposal::*;
Expand Down
15 changes: 11 additions & 4 deletions programs/futarchy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,18 @@ pub mod futarchy {
}

#[access_control(ctx.accounts.validate(&args))]
pub fn admin_approve_multisig_proposal(
ctx: Context<AdminApproveMultisigProposal>,
args: AdminApproveMultisigProposalArgs,
pub fn admin_enqueue_multisig_proposal_approval(
ctx: Context<AdminEnqueueMultisigProposalApproval>,
args: AdminEnqueueMultisigProposalApprovalArgs,
) -> Result<()> {
AdminApproveMultisigProposal::handle(ctx, args)
AdminEnqueueMultisigProposalApproval::handle(ctx, args)
}

#[access_control(ctx.accounts.validate())]
pub fn execute_multisig_proposal_approval(
ctx: Context<ExecuteMultisigProposalApproval>,
) -> Result<()> {
ExecuteMultisigProposalApproval::handle(ctx)
}

#[access_control(ctx.accounts.validate())]
Expand Down
11 changes: 11 additions & 0 deletions programs/futarchy/src/state/enqueued_multisig_proposal_approval.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use super::*;

pub const SEED_ENQUEUED_MULTISIG_PROPOSAL_APPROVAL: &[u8] = b"enqueued_approval";

#[account]
#[derive(InitSpace)]
pub struct EnqueuedMultisigProposalApproval {
pub dao: Pubkey,
pub transaction_index: u64,
pub pda_bump: u8,
}
2 changes: 2 additions & 0 deletions programs/futarchy/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
pub mod amm_position;
pub mod dao;
pub mod enqueued_multisig_proposal_approval;
pub mod futarchy_amm;
pub mod proposal;
pub mod stake_account;

pub use amm_position::*;
pub use dao::*;
pub use enqueued_multisig_proposal_approval::*;
pub use futarchy_amm::*;
pub use proposal::*;
pub use stake_account::*;
Expand Down
Loading
Loading