Skip to content
Open
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
16 changes: 15 additions & 1 deletion programs/futarchy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ pub enum FutarchyError {
EmptyProposalParamsUpdate,
#[msg("Buyback amount exceeds 25% of the treasury")]
BuybackCapExceeded,
#[msg("The total must be an exact multiple of the non-zero per-cycle amount, at least twice over")]
#[msg("Buyback total must be non-zero")]
InvalidBuybackAmount,
#[msg("Cycle frequency must be between 60 seconds and 1 year")]
InvalidBuybackCycleFrequency,
Expand All @@ -132,4 +132,18 @@ pub enum FutarchyError {
TreasuryAccountsNotSorted,
#[msg("This proposal kind's launch takes no extra accounts")]
UnexpectedLaunchAccounts,
#[msg("Spending limit account is not the canonical spending-limit PDA")]
InvalidSpendingLimitAccount,
#[msg("The DAO's team has changed since this draft was created")]
StaleTeamAddress,
#[msg("Account is not migrated to latest layout")]
AccountNotMigrated,
#[msg("A spending limit's monthly amount must be non-zero")]
InvalidSpendingLimitAmount,
#[msg("A spending limit must have at least one member")]
EmptySpendingLimitMembers,
#[msg("A spending limit's members must be unique")]
DuplicateSpendingLimitMember,
#[msg("A buyback must run at least two cycles")]
InvalidBuybackCycleCount,
}
11 changes: 0 additions & 11 deletions programs/futarchy/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,3 @@ pub struct SyncSpendingLimitEvent {
/// `None` = no limit (removed or never existed).
pub config: Option<InitialSpendingLimit>,
}

#[event]
pub struct ApplyLiquidationEvent {
pub common: CommonFields,
pub dao: Pubkey,
pub proposal: Pubkey,
pub liquidator: Pubkey,
pub base_swept: u64,
pub quote_swept: u64,
pub post_amm_state: FutarchyAmm,
}
4 changes: 4 additions & 0 deletions programs/futarchy/src/instructions/admin_cancel_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ pub struct AdminCancelProposal<'info> {

impl AdminCancelProposal<'_> {
pub fn validate(&self) -> Result<()> {
// Ensure the proposal and DAO are migrated.
Proposal::assert_migrated(&self.proposal.to_account_info())?;
Dao::assert_migrated(&self.dao.to_account_info())?;

// Unblockable proposals are censorship-proof once live: nobody, including
// the council, can cancel them. Reads the create-time snapshot so a
// live proposal keeps the flag it launched with.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ pub struct AdminEnqueueMultisigProposalApproval<'info> {

impl AdminEnqueueMultisigProposalApproval<'_> {
pub fn validate(&self, _args: &AdminEnqueueMultisigProposalApprovalArgs) -> Result<()> {
// Ensure the DAO is migrated before reading `liquidator`.
Dao::assert_migrated(&self.dao.to_account_info())?;

// On a liquidated DAO the liquidator replaces the admin id as the
// required signer. Enqueueing is the only capability the liquidator
// gains: the approve leg stays permissionless and execution is
Expand Down
163 changes: 0 additions & 163 deletions programs/futarchy/src/instructions/apply_liquidation.rs

This file was deleted.

18 changes: 18 additions & 0 deletions programs/futarchy/src/instructions/finalize_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ pub struct FinalizeProposal<'info> {

impl FinalizeProposal<'_> {
pub fn validate(&self) -> Result<()> {
// Ensure the proposal and DAO are migrated.
Proposal::assert_migrated(&self.proposal.to_account_info())?;
Dao::assert_migrated(&self.dao.to_account_info())?;

let clock = Clock::get()?;

require_gte!(
Expand Down Expand Up @@ -174,6 +178,20 @@ impl FinalizeProposal<'_> {
}
}

// In case of a hostile liquidation, set the liquidator immediately.
if new_proposal_state == ProposalState::Passed {
if let ProposalAction::HostileLiquidate { liquidator } = &proposal.action {
dao.liquidator = Some(*liquidator);

Comment thread
greptile-apps[bot] marked this conversation as resolved.
// The spending limit must be zeroed so that the estate can be swept.
// Otherwise a still-live limit member could drain the estate.
if dao.initial_spending_limit.is_some() {
dao.initial_spending_limit = None;
dao.spending_limit_dirty = true;
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}
}

// The buyback cooldown stamps on either outcome: it rate-limits an
// action the DAO consented to — draining the treasury through a
// sequence of individually reasonable votes — rather than deterring
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use super::*;

/// The venue's DCA interface bounds (Jupiter's Trigger API suite): an
/// integral order count of at least 2, an interval between a minute and a
/// year, and a start at most 30 days out.
/// The venue's DCA interface bounds (Jupiter's Trigger API suite): a total
/// split across an integral order count of at least 2, with any remainder
/// landing in the last order; an interval between a minute and a year; and a
/// start at most 30 days out. Its per-order value floor is a USD figure set by
/// venue policy, so it is deliberately not mirrored here.
pub const MIN_BUYBACK_CYCLE_COUNT: u32 = 2;
pub const MIN_BUYBACK_CYCLE_SECONDS: u32 = 60;
pub const MAX_BUYBACK_CYCLE_SECONDS: u32 = 365 * DAY_SECONDS;
pub const MAX_BUYBACK_START_DELAY_SECONDS: u32 = 30 * DAY_SECONDS;

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct InitializeBuybackTokenProposalArgs {
pub quote_amount: u64,
pub quote_amount_per_cycle: u64,
pub cycle_count: u32,
pub cycle_frequency_seconds: u32,
pub start_delay_seconds: u32,
pub min_price: Option<u64>,
Expand All @@ -27,23 +30,12 @@ impl InitializeBuybackTokenProposal<'_> {
pub fn validate(&self, args: &InitializeBuybackTokenProposalArgs) -> Result<()> {
self.typed_initialize_accounts.validate()?;

// The venue takes an integral order count with a two-order minimum,
// so the total must be an exact multiple of the per-cycle amount, at
// least twice over. A zero total falls out of the two-order check.
require_gt!(
args.quote_amount_per_cycle,
0,
FutarchyError::InvalidBuybackAmount
);
require_eq!(
args.quote_amount % args.quote_amount_per_cycle,
0,
FutarchyError::InvalidBuybackAmount
);
// A zero total is a mandate to buy nothing.
require_gt!(args.quote_amount, 0, FutarchyError::InvalidBuybackAmount);
require_gte!(
args.quote_amount / args.quote_amount_per_cycle,
2,
FutarchyError::InvalidBuybackAmount
args.cycle_count,
MIN_BUYBACK_CYCLE_COUNT,
FutarchyError::InvalidBuybackCycleCount
);

require_gte!(
Expand Down Expand Up @@ -78,10 +70,10 @@ impl InitializeBuybackTokenProposal<'_> {
None => "none".to_string(),
};
let memo = format!(
"metadao-buyback/1 proposal={} spend={} per_cycle={} cycle_seconds={} start_delay={} min_price={} max_price={}",
"metadao-buyback/1 proposal={} spend={} cycles={} cycle_seconds={} start_delay={} min_price={} max_price={}",
typed_initialize_accounts.proposal.key(),
args.quote_amount,
args.quote_amount_per_cycle,
args.cycle_count,
args.cycle_frequency_seconds,
args.start_delay_seconds,
format_price(args.min_price),
Expand All @@ -94,7 +86,7 @@ impl InitializeBuybackTokenProposal<'_> {
&[memo_ix],
ProposalAction::BuybackToken {
quote_amount: args.quote_amount,
quote_amount_per_cycle: args.quote_amount_per_cycle,
cycle_count: args.cycle_count,
cycle_frequency_seconds: args.cycle_frequency_seconds,
start_delay_seconds: args.start_delay_seconds,
min_price: args.min_price,
Expand Down
5 changes: 1 addition & 4 deletions programs/futarchy/src/instructions/initialize_dao.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,7 @@ impl InitializeDao<'_> {
)?;

if let Some(initial_spending_limit) = initial_spending_limit.clone() {
require_gte!(
MAX_SPENDING_LIMIT_MEMBERS,
initial_spending_limit.members.len()
);
initial_spending_limit.validate()?;

squads_multisig_program::cpi::multisig_add_spending_limit(
CpiContext::new_with_signer(
Expand Down
Loading
Loading