diff --git a/programs/bid_wall/src/instructions/close_bid_wall.rs b/programs/bid_wall/src/instructions/close_bid_wall.rs index 4fcd82bd9..dcdd50169 100644 --- a/programs/bid_wall/src/instructions/close_bid_wall.rs +++ b/programs/bid_wall/src/instructions/close_bid_wall.rs @@ -19,9 +19,6 @@ pub struct CloseBidWall<'info> { )] pub bid_wall: Account<'info, BidWall>, - #[account(mut)] - pub payer: Signer<'info>, - /// CHECK: used for constraints #[account(mut, address = bid_wall.authority)] pub authority: UncheckedAccount<'info>, diff --git a/programs/bid_wall/src/instructions/collect_fees.rs b/programs/bid_wall/src/instructions/collect_fees.rs index 957f9fe6f..5988f343f 100644 --- a/programs/bid_wall/src/instructions/collect_fees.rs +++ b/programs/bid_wall/src/instructions/collect_fees.rs @@ -34,7 +34,6 @@ pub struct CollectFees<'info> { pub quote_mint: Account<'info, Mint>, pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, } impl CollectFees<'_> { diff --git a/programs/bid_wall/src/instructions/sell_tokens.rs b/programs/bid_wall/src/instructions/sell_tokens.rs index f5d4662e9..2e7278a4c 100644 --- a/programs/bid_wall/src/instructions/sell_tokens.rs +++ b/programs/bid_wall/src/instructions/sell_tokens.rs @@ -20,7 +20,6 @@ pub struct SellTokens<'info> { #[account(mut, has_one = base_mint)] pub bid_wall: Account<'info, BidWall>, - #[account(mut)] pub user: Signer<'info>, #[account(mut, associated_token::mint = base_mint, associated_token::authority = user)] @@ -46,7 +45,6 @@ pub struct SellTokens<'info> { pub quote_mint: Account<'info, Mint>, pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, } // User sells tokens into the bid wall at the initial NAV per token, adjusted for treasury balance changes. diff --git a/programs/futarchy/src/instructions/admin_cancel_proposal.rs b/programs/futarchy/src/instructions/admin_cancel_proposal.rs index 6a49dcdc5..36ae6406b 100644 --- a/programs/futarchy/src/instructions/admin_cancel_proposal.rs +++ b/programs/futarchy/src/instructions/admin_cancel_proposal.rs @@ -64,7 +64,6 @@ pub struct AdminCancelProposal<'info> { #[account(mut, address = base_vault.underlying_token_account)] pub base_vault_underlying_token_account: Box>, - #[account(mut)] pub admin: Signer<'info>, } diff --git a/programs/futarchy/src/instructions/collect_meteora_damm_fees.rs b/programs/futarchy/src/instructions/collect_meteora_damm_fees.rs index 3d8c387ce..c784a8721 100644 --- a/programs/futarchy/src/instructions/collect_meteora_damm_fees.rs +++ b/programs/futarchy/src/instructions/collect_meteora_damm_fees.rs @@ -42,7 +42,7 @@ pub struct CollectMeteoraDammFees<'info> { #[account(mut, seeds = [squads_multisig_program::SEED_PREFIX, squads_multisig_program::SEED_MULTISIG, dao.key().as_ref()], bump, seeds::program = squads_program)] pub squads_multisig: Account<'info, squads_multisig_program::Multisig>, /// CHECK: signer for the squads transaction, checked by squads program - #[account(seeds = [squads_multisig_program::SEED_PREFIX, squads_multisig.key().as_ref(), squads_multisig_program::SEED_VAULT, 0_u8.to_le_bytes().as_ref()], bump, seeds::program = squads_program)] + #[account(mut, seeds = [squads_multisig_program::SEED_PREFIX, squads_multisig.key().as_ref(), squads_multisig_program::SEED_VAULT, 0_u8.to_le_bytes().as_ref()], bump, seeds::program = squads_program)] pub squads_multisig_vault: UncheckedAccount<'info>, /// CHECK: squads transaction, initialized by squads multisig program, checked by squads multisig program #[account(mut)] @@ -411,9 +411,8 @@ fn compile_transaction_message( // Track account metadata: (is_signer, is_writable) let mut key_meta_map: BTreeMap = BTreeMap::new(); - // Add vault as a signer (it will sign the vault transaction) - // Writability is determined by whether it appears as writable in instruction accounts - key_meta_map.insert(*vault_key, (true, false)); + // Add vault as a writable signer (matches Squads SDK payer semantics) + key_meta_map.insert(*vault_key, (true, true)); // Collect all accounts from instructions, merging their flags with OR for ix in instructions { @@ -435,14 +434,14 @@ fn compile_transaction_message( for (pubkey, (is_signer, is_writable)) in &key_meta_map { if *is_signer && *is_writable { - writable_signers.push(*pubkey); - } else if *is_signer { - // Vault key should be first among readonly signers + // Vault key should be first among writable signers (matches Squads SDK) if *pubkey == *vault_key { - readonly_signers.insert(0, *pubkey); + writable_signers.insert(0, *pubkey); } else { - readonly_signers.push(*pubkey); + writable_signers.push(*pubkey); } + } else if *is_signer { + readonly_signers.push(*pubkey); } else if *is_writable { writable_non_signers.push(*pubkey); } else { diff --git a/programs/futarchy/src/instructions/execute_spending_limit_change.rs b/programs/futarchy/src/instructions/execute_spending_limit_change.rs index 644f84d88..a6a66f88c 100644 --- a/programs/futarchy/src/instructions/execute_spending_limit_change.rs +++ b/programs/futarchy/src/instructions/execute_spending_limit_change.rs @@ -4,7 +4,6 @@ use anchor_lang::Discriminator; use squads_multisig_program::program::SquadsMultisigProgram; #[derive(Accounts)] -#[event_cpi] pub struct ExecuteSpendingLimitChange<'info> { #[account( mut, has_one = dao, has_one = squads_proposal, @@ -40,8 +39,6 @@ impl<'info, 'c: 'info> ExecuteSpendingLimitChange<'info> { squads_multisig, squads_multisig_program, vault_transaction, - event_authority: _, - program: _, } = ctx.accounts; let message = &vault_transaction.message; diff --git a/programs/futarchy/src/instructions/launch_proposal.rs b/programs/futarchy/src/instructions/launch_proposal.rs index 06d0defc2..1df9d502f 100644 --- a/programs/futarchy/src/instructions/launch_proposal.rs +++ b/programs/futarchy/src/instructions/launch_proposal.rs @@ -157,6 +157,8 @@ impl LaunchProposal<'_> { // Update proposal state to Pending and set timestamp enqueued proposal.state = ProposalState::Pending; proposal.timestamp_enqueued = clock.unix_timestamp; + // Additionally, set the duration once more in case it was updated since the proposal was created + proposal.duration_in_seconds = dao.seconds_per_proposal; dao.seq_num += 1; diff --git a/programs/futarchy/src/instructions/stake_to_proposal.rs b/programs/futarchy/src/instructions/stake_to_proposal.rs index 0a0281d26..333075b46 100644 --- a/programs/futarchy/src/instructions/stake_to_proposal.rs +++ b/programs/futarchy/src/instructions/stake_to_proposal.rs @@ -37,7 +37,6 @@ pub struct StakeToProposal<'info> { #[account(mut)] pub payer: Signer<'info>, pub token_program: Program<'info, Token>, - pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, } @@ -71,7 +70,6 @@ impl StakeToProposal<'_> { staker, payer: _, token_program, - associated_token_program: _, system_program: _, event_authority: _, program: _, diff --git a/programs/futarchy/src/instructions/unstake_from_proposal.rs b/programs/futarchy/src/instructions/unstake_from_proposal.rs index c29328de0..671874656 100644 --- a/programs/futarchy/src/instructions/unstake_from_proposal.rs +++ b/programs/futarchy/src/instructions/unstake_from_proposal.rs @@ -9,19 +9,26 @@ pub struct UnstakeFromProposalParams { #[instruction(args: UnstakeFromProposalParams)] #[event_cpi] pub struct UnstakeFromProposal<'info> { - #[account(mut)] + #[account( + mut, + has_one = dao, + )] pub proposal: Box>, - #[account(mut)] - pub dao: Box>, #[account( mut, - associated_token::mint = dao.base_mint, + has_one = base_mint, + )] + pub dao: Box>, + #[account( + init_if_needed, + payer = staker, + associated_token::mint = base_mint, associated_token::authority = staker, )] pub staker_base_account: Box>, #[account( mut, - associated_token::mint = dao.base_mint, + associated_token::mint = base_mint, associated_token::authority = proposal, )] pub proposal_base_account: Box>, @@ -31,8 +38,12 @@ pub struct UnstakeFromProposal<'info> { bump = stake_account.bump, )] pub stake_account: Box>, + #[account(address = dao.base_mint)] + pub base_mint: Account<'info, Mint>, + #[account(mut)] pub staker: Signer<'info>, pub token_program: Program<'info, Token>, + pub system_program: Program<'info, System>, pub associated_token_program: Program<'info, AssociatedToken>, } @@ -61,9 +72,11 @@ impl UnstakeFromProposal<'_> { stake_account, staker, token_program, - associated_token_program: _, event_authority: _, program: _, + system_program: _, + associated_token_program: _, + base_mint: _, } = ctx.accounts; let UnstakeFromProposalParams { amount } = params; diff --git a/programs/futarchy/src/state/futarchy_amm.rs b/programs/futarchy/src/state/futarchy_amm.rs index 9344d9d75..f0cfeb555 100644 --- a/programs/futarchy/src/state/futarchy_amm.rs +++ b/programs/futarchy/src/state/futarchy_amm.rs @@ -272,7 +272,7 @@ pub struct TwapOracle { /// /// Assuming latest observations are as big as possible (u64::MAX * 1e12), /// we can store 18 million seconds worth of observations, which turns out to - /// be ~208 days. + /// be ~213 days. /// /// Assuming that latest observations are 100x smaller than they could theoretically /// be, we can store ~57 years worth of them. Even this is a very @@ -404,9 +404,9 @@ impl Pool { .try_into() .unwrap(); - // if this saturates, the aggregator will wrap back to 0, so this value doesn't - // really matter. we just can't panic. - let weighted_observation = last_observation.saturating_mul(time_difference); + // wrapping_mul ensures we don't panic in case of overflow + // Theoretically, wrapping can occur, but it's astronomically unlikely + let weighted_observation = last_observation.wrapping_mul(time_difference); oracle.aggregator.wrapping_add(weighted_observation) }; @@ -462,7 +462,9 @@ impl Pool { // include the final interval that hasn't been accumulated yet let final_interval = (current_timestamp - self.oracle.last_updated_timestamp) as u128; - let final_contribution = self.oracle.last_observation.saturating_mul(final_interval); + // wrapping_mul ensures we don't panic in case of overflow + // Theoretically, wrapping can occur, but it's astronomically unlikely + let final_contribution = self.oracle.last_observation.wrapping_mul(final_interval); let total_aggregator = self.oracle.aggregator.wrapping_add(final_contribution); Ok(total_aggregator / seconds_passed) diff --git a/programs/performance_package_v2/src/error.rs b/programs/performance_package_v2/src/error.rs index 87075943e..b987da3b9 100644 --- a/programs/performance_package_v2/src/error.rs +++ b/programs/performance_package_v2/src/error.rs @@ -42,4 +42,6 @@ pub enum PerformancePackageError { NoChangesProposed, #[msg("min_duration exceeds maximum allowed (365 days)")] MinDurationTooLarge, + #[msg("Change request is stale: PP was recreated or the proposing party has changed")] + StaleChangeRequest, } diff --git a/programs/performance_package_v2/src/events.rs b/programs/performance_package_v2/src/events.rs index b727482a4..77900db56 100644 --- a/programs/performance_package_v2/src/events.rs +++ b/programs/performance_package_v2/src/events.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use crate::{OracleReader, ProposerType, RewardFunction}; +use crate::{OracleReader, RewardFunction}; /// Common fields included in all events for consistent metadata. #[derive(AnchorSerialize, AnchorDeserialize, Clone)] @@ -58,7 +58,7 @@ pub struct ChangeProposedEvent { pub common: CommonFields, pub performance_package: Pubkey, pub change_request: Pubkey, - pub proposer_type: ProposerType, + pub proposer: Pubkey, pub pda_nonce: u32, pub new_recipient: Option, pub new_oracle_reader: Option, diff --git a/programs/performance_package_v2/src/instructions/complete_unlock.rs b/programs/performance_package_v2/src/instructions/complete_unlock.rs index 209cea06c..4f8a7e980 100644 --- a/programs/performance_package_v2/src/instructions/complete_unlock.rs +++ b/programs/performance_package_v2/src/instructions/complete_unlock.rs @@ -1,8 +1,5 @@ use anchor_lang::prelude::*; -use anchor_spl::{ - associated_token::AssociatedToken, - token::{Mint, Token, TokenAccount}, -}; +use anchor_spl::token::{Mint, Token, TokenAccount}; use mint_governor::{ cpi::{accounts::MintTokens, mint_tokens}, program::MintGovernor as MintGovernorProgram, @@ -54,8 +51,6 @@ pub struct CompleteUnlock<'info> { pub token_program: Program<'info, Token>, - pub associated_token_program: Program<'info, AssociatedToken>, - pub mint_governor_program: Program<'info, MintGovernorProgram>, /// CHECK: checked by mint_governor program diff --git a/programs/performance_package_v2/src/instructions/execute_change.rs b/programs/performance_package_v2/src/instructions/execute_change.rs index 7163ef79f..ff11e11c9 100644 --- a/programs/performance_package_v2/src/instructions/execute_change.rs +++ b/programs/performance_package_v2/src/instructions/execute_change.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use crate::{ ChangeExecutedEvent, ChangeRequest, CommonFields, PackageStatus, PerformancePackage, - PerformancePackageError, ProposerType, + PerformancePackageError, }; #[event_cpi] @@ -31,22 +31,29 @@ impl ExecuteChange<'_> { let cr = &self.change_request; let executor = self.executor.key(); - // Executor must be the opposite party from the proposer - match cr.proposer_type { - ProposerType::Authority => { - require_keys_eq!( - executor, - pp.recipient, - PerformancePackageError::InvalidExecutor - ); - } - ProposerType::Recipient => { - require_keys_eq!( - executor, - pp.authority, - PerformancePackageError::InvalidExecutor - ); - } + // Reject CRs from a prior PP incarnation (close/recreate) + require_eq!( + cr.pp_created_at_timestamp, + pp.created_at_timestamp, + PerformancePackageError::StaleChangeRequest + ); + + // Proposer must still be the current authority or recipient, + // and executor must be the opposite party. + if cr.proposer == pp.authority { + require_keys_eq!( + executor, + pp.recipient, + PerformancePackageError::InvalidExecutor + ); + } else if cr.proposer == pp.recipient { + require_keys_eq!( + executor, + pp.authority, + PerformancePackageError::InvalidExecutor + ); + } else { + return Err(PerformancePackageError::StaleChangeRequest.into()); } // Config changes (oracle/reward function) can only happen when Locked diff --git a/programs/performance_package_v2/src/instructions/initialize_performance_package.rs b/programs/performance_package_v2/src/instructions/initialize_performance_package.rs index 5b24451f7..a8345e936 100644 --- a/programs/performance_package_v2/src/instructions/initialize_performance_package.rs +++ b/programs/performance_package_v2/src/instructions/initialize_performance_package.rs @@ -61,6 +61,8 @@ impl InitializePerformancePackage<'_> { } pub fn handle(ctx: Context, args: InitializePerformancePackageArgs) -> Result<()> { + let clock = Clock::get()?; + ctx.accounts .performance_package .set_inner(PerformancePackage { @@ -73,13 +75,12 @@ impl InitializePerformancePackage<'_> { reward_function: args.reward_function, status: PackageStatus::Locked, min_unlock_timestamp: args.min_unlock_timestamp, + created_at_timestamp: clock.unix_timestamp, total_rewards_paid_out: 0, seq_num: 0, create_key: ctx.accounts.create_key.key(), bump: ctx.bumps.performance_package, }); - - let clock = Clock::get()?; let pp = &ctx.accounts.performance_package; emit_cpi!(PerformancePackageCreatedEvent { diff --git a/programs/performance_package_v2/src/instructions/propose_change.rs b/programs/performance_package_v2/src/instructions/propose_change.rs index 8a6f80caf..2ca0b352e 100644 --- a/programs/performance_package_v2/src/instructions/propose_change.rs +++ b/programs/performance_package_v2/src/instructions/propose_change.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use crate::{ ChangeProposedEvent, ChangeRequest, CommonFields, OracleReader, PerformancePackage, - PerformancePackageError, ProposerType, RewardFunction, CHANGE_REQUEST_SEED, + PerformancePackageError, RewardFunction, CHANGE_REQUEST_SEED, }; #[derive(AnchorSerialize, AnchorDeserialize, Clone)] @@ -77,21 +77,13 @@ impl ProposeChange<'_> { let pp = &mut ctx.accounts.performance_package; let proposer_key = ctx.accounts.proposer.key(); - // Determine proposer type - // Authority could theoretically change during change proposal, - // so we need this ProposerType to know who needs to sign the change request. - let proposer_type = if proposer_key == pp.authority { - ProposerType::Authority - } else { - ProposerType::Recipient - }; - let clock = Clock::get()?; // Initialize the change request ctx.accounts.change_request.set_inner(ChangeRequest { performance_package: pp.key(), - proposer_type, + proposer: proposer_key, + pp_created_at_timestamp: pp.created_at_timestamp, proposed_at: clock.unix_timestamp, pda_nonce: args.pda_nonce, bump: ctx.bumps.change_request, @@ -111,7 +103,7 @@ impl ProposeChange<'_> { }, performance_package: pp.key(), change_request: ctx.accounts.change_request.key(), - proposer_type, + proposer: proposer_key, pda_nonce: args.pda_nonce, new_recipient: args.new_recipient, new_oracle_reader: args.new_oracle_reader, diff --git a/programs/performance_package_v2/src/state/change_request.rs b/programs/performance_package_v2/src/state/change_request.rs index c39a399be..c89091954 100644 --- a/programs/performance_package_v2/src/state/change_request.rs +++ b/programs/performance_package_v2/src/state/change_request.rs @@ -2,13 +2,6 @@ use anchor_lang::prelude::*; use super::{OracleReader, RewardFunction}; -/// Who proposed the change. -#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Eq, InitSpace)] -pub enum ProposerType { - Authority, - Recipient, -} - /// Temporary account for two-party approval flow. /// Seeds: `["change_request", performance_package, proposer, pda_nonce.to_le_bytes()]` #[account] @@ -16,8 +9,10 @@ pub enum ProposerType { pub struct ChangeRequest { /// The performance package this change applies to pub performance_package: Pubkey, - /// Who proposed this change - pub proposer_type: ProposerType, + /// The proposer's pubkey at proposal time + pub proposer: Pubkey, + /// PP's created_at_timestamp at proposal time; used to detect stale CRs after close/recreate + pub pp_created_at_timestamp: i64, /// When the change was proposed pub proposed_at: i64, /// For unique PDA derivation (allows multiple concurrent proposals) diff --git a/programs/performance_package_v2/src/state/performance_package.rs b/programs/performance_package_v2/src/state/performance_package.rs index df8a0aaa1..6d73b033b 100644 --- a/programs/performance_package_v2/src/state/performance_package.rs +++ b/programs/performance_package_v2/src/state/performance_package.rs @@ -70,8 +70,18 @@ fn read_futarchy_aggregator( // produce an artificially low effective aggregator, distorting the TWAP. let clock = Clock::get()?; let twap_start_timestamp = oracle.created_at_timestamp + oracle.start_delay_seconds as i64; - require!( - clock.unix_timestamp >= twap_start_timestamp, + require_gte!( + clock.unix_timestamp, + twap_start_timestamp, + PerformancePackageError::OracleInvalidState + ); + // Ensure at least one update_twap has occurred after the start delay. + // Without this, the aggregator is still zero and the effective_aggregator + // projection below would include phantom accumulation over the start delay + // period (time_since_update measured from creation, not from first real update). + require_gte!( + oracle.last_updated_timestamp, + twap_start_timestamp, PerformancePackageError::OracleInvalidState ); let time_since_update = clock @@ -79,7 +89,7 @@ fn read_futarchy_aggregator( .saturating_sub(oracle.last_updated_timestamp) as u128; let effective_aggregator = oracle .aggregator - .wrapping_add(oracle.last_observation.saturating_mul(time_since_update)); + .wrapping_add(oracle.last_observation.wrapping_mul(time_since_update)); Ok((effective_aggregator, clock.unix_timestamp)) } @@ -94,8 +104,9 @@ impl OracleReader { } &OracleReader::FutarchyTwap { min_duration, .. } => { // min_duration must be > 0 to avoid division by zero in TWAP calculation - require!( - min_duration > 0, + require_gt!( + min_duration, + 0, PerformancePackageError::InvalidVestingSchedule ); require_gte!( @@ -204,7 +215,7 @@ impl OracleReader { let time_delta = end_time - start_time; // Ensure time_delta > 0 to avoid division by zero - require!(time_delta > 0, PerformancePackageError::OracleInvalidState); + require_gt!(time_delta, 0, PerformancePackageError::OracleInvalidState); // Calculate TWAP: (end_value - start_value) / time_delta // Note: end_value should always be >= start_value since aggregator is cumulative @@ -246,21 +257,22 @@ impl RewardFunction { pub fn validate(&self) -> Result<()> { match self { RewardFunction::CliffLinear { - start_value, cliff_value, end_value, cliff_amount, total_amount, } => { - // start_value <= cliff_value <= end_value - require!( - start_value <= cliff_value && cliff_value <= end_value, + // end_value must be greater than or equal to cliff_value + require_gte!( + end_value, + cliff_value, PerformancePackageError::InvalidVestingSchedule ); - // cliff_amount <= total_amount - require!( - cliff_amount <= total_amount, + // total_amount must be greater than or equal to cliff_amount + require_gte!( + total_amount, + cliff_amount, PerformancePackageError::InvalidVestingSchedule ); } @@ -276,12 +288,14 @@ impl RewardFunction { for window in tranches.windows(2) { let prev = &window[0]; let curr = &window[1]; - require!( - prev.threshold < curr.threshold, + require_gt!( + curr.threshold, + prev.threshold, PerformancePackageError::InvalidTranches ); - require!( - prev.cumulative_amount <= curr.cumulative_amount, + require_gte!( + curr.cumulative_amount, + prev.cumulative_amount, PerformancePackageError::InvalidTranches ); } @@ -295,17 +309,11 @@ impl RewardFunction { pub fn calculate(&self, value: u128) -> Result { match self { &RewardFunction::CliffLinear { - start_value, cliff_value, end_value, cliff_amount, total_amount, } => { - // Before start: 0 rewards - if value < start_value { - return Ok(0); - } - // Before cliff: 0 rewards if value < cliff_value { return Ok(0); @@ -369,9 +377,8 @@ pub struct ThresholdTranche { pub enum RewardFunction { /// Cliff + Linear: cliff_amount at cliff_value, then linear accrual to total_amount at end_value /// Works with any oracle value (e.g., time, price, or other metrics) - /// For no-cliff behavior, set cliff_value = start_value and cliff_amount = 0 + /// For no-cliff behavior, set cliff_amount = 0 CliffLinear { - start_value: u128, cliff_value: u128, end_value: u128, cliff_amount: u64, @@ -414,6 +421,8 @@ pub struct PerformancePackage { pub status: PackageStatus, /// Can't start unlock before this time pub min_unlock_timestamp: i64, + /// Timestamp when this PP was created; used to invalidate stale ChangeRequests + pub created_at_timestamp: i64, /// Cumulative tokens minted to the recipient pub total_rewards_paid_out: u64, diff --git a/programs/price_based_performance_package/src/instructions/burn_performance_package.rs b/programs/price_based_performance_package/src/instructions/burn_performance_package.rs index 1cc5df46c..b2c0f467d 100644 --- a/programs/price_based_performance_package/src/instructions/burn_performance_package.rs +++ b/programs/price_based_performance_package/src/instructions/burn_performance_package.rs @@ -11,7 +11,6 @@ pub mod admin { } #[derive(Accounts)] -#[event_cpi] pub struct BurnPerformancePackage<'info> { #[account( mut, diff --git a/programs/price_based_performance_package/src/instructions/complete_unlock.rs b/programs/price_based_performance_package/src/instructions/complete_unlock.rs index ad9013cea..a39cad925 100644 --- a/programs/price_based_performance_package/src/instructions/complete_unlock.rs +++ b/programs/price_based_performance_package/src/instructions/complete_unlock.rs @@ -125,7 +125,8 @@ impl CompleteUnlock<'_> { ); // Calculate TWAP: (current_aggregator - start_aggregator) / time_passed - let aggregator_change = current_aggregator.saturating_sub(start_aggregator); + // wrapping_sub ensures we get the correct difference in case of aggregator wrapping + let aggregator_change = current_aggregator.wrapping_sub(start_aggregator); let twap_price = aggregator_change / time_passed as u128; let mut tokens_to_unlock = 0; diff --git a/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs b/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs index 1f52ba6f2..c57e36713 100644 --- a/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs +++ b/programs/price_based_performance_package/src/instructions/initialize_performance_package.rs @@ -126,13 +126,6 @@ impl InitializePerformancePackage<'_> { let clock = Clock::get()?; - // Validate that unlock timestamp is in the future - require_gt!( - min_unlock_timestamp, - clock.unix_timestamp, - PriceBasedPerformancePackageError::UnlockTimestampInThePast - ); - let total_token_amount = tranches.iter().try_fold(0u64, |acc, tranche| { acc.checked_add(tranche.token_amount).ok_or(error!( PriceBasedPerformancePackageError::TotalTokenAmountOverflow diff --git a/programs/v06_launchpad/src/instructions/claim.rs b/programs/v06_launchpad/src/instructions/claim.rs index 091f477d4..e81626125 100644 --- a/programs/v06_launchpad/src/instructions/claim.rs +++ b/programs/v06_launchpad/src/instructions/claim.rs @@ -19,6 +19,7 @@ pub struct Claim<'info> { #[account( mut, + has_one = launch, has_one = funder, seeds = [b"funding_record", launch.key().as_ref(), funder.key().as_ref()], bump = funding_record.pda_bump @@ -28,7 +29,6 @@ pub struct Claim<'info> { /// CHECK: just a signer pub launch_signer: UncheckedAccount<'info>, - #[account(mut)] pub base_mint: Account<'info, Mint>, #[account(mut)] @@ -45,7 +45,6 @@ pub struct Claim<'info> { pub funder_token_account: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, } impl Claim<'_> { diff --git a/programs/v06_launchpad/src/instructions/fund.rs b/programs/v06_launchpad/src/instructions/fund.rs index 662a7b06b..a23e6cdfe 100644 --- a/programs/v06_launchpad/src/instructions/fund.rs +++ b/programs/v06_launchpad/src/instructions/fund.rs @@ -10,7 +10,6 @@ use crate::state::{FundingRecord, Launch, LaunchState}; pub struct Fund<'info> { #[account( mut, - has_one = launch_signer, has_one = launch_quote_vault, )] pub launch: Account<'info, Launch>, @@ -24,9 +23,6 @@ pub struct Fund<'info> { )] pub funding_record: Account<'info, FundingRecord>, - /// CHECK: just a signer - pub launch_signer: UncheckedAccount<'info>, - #[account(mut)] pub launch_quote_vault: Account<'info, TokenAccount>, @@ -62,7 +58,7 @@ impl Fund<'_> { let clock = Clock::get()?; - require_gte!( + require_gt!( self.launch.unix_timestamp_started.unwrap() + self.launch.seconds_for_launch as i64, clock.unix_timestamp, LaunchpadError::LaunchExpired diff --git a/programs/v06_launchpad/src/instructions/initialize_launch.rs b/programs/v06_launchpad/src/instructions/initialize_launch.rs index 21745bc99..c870213ef 100644 --- a/programs/v06_launchpad/src/instructions/initialize_launch.rs +++ b/programs/v06_launchpad/src/instructions/initialize_launch.rs @@ -143,6 +143,19 @@ impl InitializeLaunch<'_> { LaunchpadError::InvalidMonthlySpendingLimitMembers ); + require!( + !args.monthly_spending_limit_members.is_empty(), + LaunchpadError::InvalidMonthlySpendingLimitMembers + ); + + let mut sorted_members = args.monthly_spending_limit_members.clone(); + sorted_members.sort(); + let has_duplicates = sorted_members.windows(2).any(|win| win[0] == win[1]); + require!( + !has_duplicates, + LaunchpadError::InvalidMonthlySpendingLimitMembers + ); + require_gte!( MAX_PREMINE, args.performance_package_token_amount, diff --git a/programs/v06_launchpad/src/instructions/refund.rs b/programs/v06_launchpad/src/instructions/refund.rs index 06a05070f..9d41772dc 100644 --- a/programs/v06_launchpad/src/instructions/refund.rs +++ b/programs/v06_launchpad/src/instructions/refund.rs @@ -17,6 +17,7 @@ pub struct Refund<'info> { #[account( mut, + has_one = launch, has_one = funder, seeds = [b"funding_record", launch.key().as_ref(), funder.key().as_ref()], bump = funding_record.pda_bump @@ -36,7 +37,6 @@ pub struct Refund<'info> { pub funder_quote_account: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, } impl Refund<'_> { diff --git a/programs/v06_launchpad/src/instructions/return_funds.rs b/programs/v06_launchpad/src/instructions/return_funds.rs index b1fb3d5eb..05483fe21 100644 --- a/programs/v06_launchpad/src/instructions/return_funds.rs +++ b/programs/v06_launchpad/src/instructions/return_funds.rs @@ -45,7 +45,6 @@ pub struct ReturnFunds<'info> { pub recipient_quote_account: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, } impl ReturnFunds<'_> { diff --git a/programs/v07_launchpad/src/instructions/claim.rs b/programs/v07_launchpad/src/instructions/claim.rs index ad7ce4980..ffb314636 100644 --- a/programs/v07_launchpad/src/instructions/claim.rs +++ b/programs/v07_launchpad/src/instructions/claim.rs @@ -19,6 +19,7 @@ pub struct Claim<'info> { #[account( mut, + has_one = launch, has_one = funder, seeds = [b"funding_record", launch.key().as_ref(), funder.key().as_ref()], bump = funding_record.pda_bump @@ -28,7 +29,6 @@ pub struct Claim<'info> { /// CHECK: just a signer pub launch_signer: UncheckedAccount<'info>, - #[account(mut)] pub base_mint: Account<'info, Mint>, #[account(mut)] @@ -45,7 +45,6 @@ pub struct Claim<'info> { pub funder_token_account: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, } impl Claim<'_> { diff --git a/programs/v07_launchpad/src/instructions/claim_additional_token_allocation.rs b/programs/v07_launchpad/src/instructions/claim_additional_token_allocation.rs index 3da0c8cd9..1d1928b53 100644 --- a/programs/v07_launchpad/src/instructions/claim_additional_token_allocation.rs +++ b/programs/v07_launchpad/src/instructions/claim_additional_token_allocation.rs @@ -21,7 +21,6 @@ pub struct ClaimAdditionalTokenAllocation<'info> { pub payer: Signer<'info>, /// CHECK: just a signer - #[account(mut)] pub launch_signer: UncheckedAccount<'info>, #[account( @@ -31,7 +30,7 @@ pub struct ClaimAdditionalTokenAllocation<'info> { )] pub launch_base_vault: Account<'info, TokenAccount>, - #[account(mut, address = launch.base_mint)] + #[account(address = launch.base_mint)] pub base_mint: Account<'info, Mint>, /// CHECK: The recipient of the additional tokens, used for constraints, explicitly checked in validate diff --git a/programs/v07_launchpad/src/instructions/fund.rs b/programs/v07_launchpad/src/instructions/fund.rs index 9c45caa69..6c9673fc8 100644 --- a/programs/v07_launchpad/src/instructions/fund.rs +++ b/programs/v07_launchpad/src/instructions/fund.rs @@ -10,7 +10,6 @@ use crate::state::{FundingRecord, Launch, LaunchState}; pub struct Fund<'info> { #[account( mut, - has_one = launch_signer, has_one = launch_quote_vault, )] pub launch: Account<'info, Launch>, @@ -24,9 +23,6 @@ pub struct Fund<'info> { )] pub funding_record: Account<'info, FundingRecord>, - /// CHECK: just a signer - pub launch_signer: UncheckedAccount<'info>, - #[account(mut)] pub launch_quote_vault: Account<'info, TokenAccount>, diff --git a/programs/v07_launchpad/src/instructions/initialize_performance_package.rs b/programs/v07_launchpad/src/instructions/initialize_performance_package.rs index 5fe29aa9a..2c8ef0759 100644 --- a/programs/v07_launchpad/src/instructions/initialize_performance_package.rs +++ b/programs/v07_launchpad/src/instructions/initialize_performance_package.rs @@ -26,7 +26,6 @@ pub struct InitializePerformancePackage<'info> { pub payer: Signer<'info>, /// CHECK: just a signer - #[account(mut)] pub launch_signer: UncheckedAccount<'info>, #[account( diff --git a/programs/v07_launchpad/src/instructions/refund.rs b/programs/v07_launchpad/src/instructions/refund.rs index e947ccaee..cc5afdbf4 100644 --- a/programs/v07_launchpad/src/instructions/refund.rs +++ b/programs/v07_launchpad/src/instructions/refund.rs @@ -17,6 +17,7 @@ pub struct Refund<'info> { #[account( mut, + has_one = launch, has_one = funder, seeds = [b"funding_record", launch.key().as_ref(), funder.key().as_ref()], bump = funding_record.pda_bump @@ -36,7 +37,6 @@ pub struct Refund<'info> { pub funder_quote_account: Account<'info, TokenAccount>, pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, } impl Refund<'_> { diff --git a/sdk/package.json b/sdk/package.json index 303ce3015..96ecb6885 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@metadaoproject/futarchy", - "version": "0.7.2-alpha.0", + "version": "0.7.3-alpha.1", "type": "module", "main": "dist/index.js", "module": "dist/index.js", diff --git a/sdk/src/v0.6/LaunchpadClient.ts b/sdk/src/v0.6/LaunchpadClient.ts index 3a7423820..77097f3ce 100644 --- a/sdk/src/v0.6/LaunchpadClient.ts +++ b/sdk/src/v0.6/LaunchpadClient.ts @@ -267,7 +267,6 @@ export class LaunchpadClient { fundingRecord, funder, funderQuoteAccount, - launchSigner, }); } diff --git a/sdk/src/v0.6/types/launchpad.ts b/sdk/src/v0.6/types/launchpad.ts index 35c57a81d..bbfbca5a7 100644 --- a/sdk/src/v0.6/types/launchpad.ts +++ b/sdk/src/v0.6/types/launchpad.ts @@ -134,11 +134,6 @@ export type Launchpad = { isMut: true; isSigner: false; }, - { - name: "launchSigner"; - isMut: false; - isSigner: false; - }, { name: "launchQuoteVault"; isMut: true; @@ -479,11 +474,6 @@ export type Launchpad = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -540,11 +530,6 @@ export type Launchpad = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -617,11 +602,6 @@ export type Launchpad = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -1561,11 +1541,6 @@ export const IDL: Launchpad = { isMut: true, isSigner: false, }, - { - name: "launchSigner", - isMut: false, - isSigner: false, - }, { name: "launchQuoteVault", isMut: true, @@ -1906,11 +1881,6 @@ export const IDL: Launchpad = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, @@ -1967,11 +1937,6 @@ export const IDL: Launchpad = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, @@ -2044,11 +2009,6 @@ export const IDL: Launchpad = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, diff --git a/sdk/src/v0.7/BidWallClient.ts b/sdk/src/v0.7/BidWallClient.ts index 862708753..7652baf70 100644 --- a/sdk/src/v0.7/BidWallClient.ts +++ b/sdk/src/v0.7/BidWallClient.ts @@ -173,7 +173,6 @@ export class BidWallClient { daoTreasury, daoTreasuryQuoteTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, - systemProgram: SystemProgram.programId, }); } @@ -205,7 +204,6 @@ export class BidWallClient { feeRecipientQuoteTokenAccount, quoteMint, tokenProgram: TOKEN_PROGRAM_ID, - systemProgram: SystemProgram.programId, }); } @@ -215,14 +213,12 @@ export class BidWallClient { baseMint, feeRecipient = METADAO_MULTISIG_VAULT, quoteMint = MAINNET_USDC, - payer = this.provider.publicKey, }: { bidWall: PublicKey; authority: PublicKey; baseMint: PublicKey; feeRecipient: PublicKey; quoteMint: PublicKey; - payer: PublicKey; }) { const bidWallQuoteTokenAccount = getAssociatedTokenAddressSync( quoteMint, @@ -242,7 +238,6 @@ export class BidWallClient { return this.bidWallProgram.methods.closeBidWall().accounts({ bidWall, - payer, authority, feeRecipient, bidWallQuoteTokenAccount, diff --git a/sdk/src/v0.7/FutarchyClient.ts b/sdk/src/v0.7/FutarchyClient.ts index 0f22606f6..55a2d5506 100644 --- a/sdk/src/v0.7/FutarchyClient.ts +++ b/sdk/src/v0.7/FutarchyClient.ts @@ -56,7 +56,6 @@ import { getAssociatedTokenAddressSync, unpackMint, TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import { sha256 } from "@noble/hashes/sha256"; import { Dao, Proposal } from "./types/index.js"; @@ -907,7 +906,6 @@ export class FutarchyClient { staker, payer, tokenProgram: TOKEN_PROGRAM_ID, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, }) .preInstructions([ @@ -952,8 +950,8 @@ export class FutarchyClient { ), stakeAccount, staker, + baseMint, tokenProgram: TOKEN_PROGRAM_ID, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, }); } diff --git a/sdk/src/v0.7/LaunchpadClient.ts b/sdk/src/v0.7/LaunchpadClient.ts index 1ef991819..47922e994 100644 --- a/sdk/src/v0.7/LaunchpadClient.ts +++ b/sdk/src/v0.7/LaunchpadClient.ts @@ -282,7 +282,6 @@ export class LaunchpadClient { fundingRecord, funder, funderQuoteAccount, - launchSigner, }); } diff --git a/sdk/src/v0.7/PerformancePackageV2Client.ts b/sdk/src/v0.7/PerformancePackageV2Client.ts index 9eec8f7a7..597b38eb5 100644 --- a/sdk/src/v0.7/PerformancePackageV2Client.ts +++ b/sdk/src/v0.7/PerformancePackageV2Client.ts @@ -2,7 +2,6 @@ import { AnchorProvider, Program } from "@coral-xyz/anchor"; import { AccountInfo, PublicKey, SystemProgram } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID, - ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, } from "@solana/spl-token"; import BN from "bn.js"; @@ -206,7 +205,6 @@ export class PerformancePackageV2Client { recipientAta, signer, tokenProgram: TOKEN_PROGRAM_ID, - associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, mintGovernorProgram: MINT_GOVERNOR_PROGRAM_ID, mintGovernorEventAuthority, }); diff --git a/sdk/src/v0.7/types/bid_wall.ts b/sdk/src/v0.7/types/bid_wall.ts index 438bea796..d54b464bd 100644 --- a/sdk/src/v0.7/types/bid_wall.ts +++ b/sdk/src/v0.7/types/bid_wall.ts @@ -98,11 +98,6 @@ export type BidWall = { isMut: true; isSigner: false; }, - { - name: "payer"; - isMut: true; - isSigner: true; - }, { name: "authority"; isMut: true; @@ -171,7 +166,7 @@ export type BidWall = { }, { name: "user"; - isMut: true; + isMut: false; isSigner: true; }, { @@ -214,11 +209,6 @@ export type BidWall = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -272,11 +262,6 @@ export type BidWall = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -831,11 +816,6 @@ export const IDL: BidWall = { isMut: true, isSigner: false, }, - { - name: "payer", - isMut: true, - isSigner: true, - }, { name: "authority", isMut: true, @@ -904,7 +884,7 @@ export const IDL: BidWall = { }, { name: "user", - isMut: true, + isMut: false, isSigner: true, }, { @@ -947,11 +927,6 @@ export const IDL: BidWall = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, @@ -1005,11 +980,6 @@ export const IDL: BidWall = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, diff --git a/sdk/src/v0.7/types/futarchy.ts b/sdk/src/v0.7/types/futarchy.ts index 17fc84cf9..ea0ab7c8d 100644 --- a/sdk/src/v0.7/types/futarchy.ts +++ b/sdk/src/v0.7/types/futarchy.ts @@ -214,11 +214,6 @@ export type Futarchy = { isMut: false; isSigner: false; }, - { - name: "associatedTokenProgram"; - isMut: false; - isSigner: false; - }, { name: "systemProgram"; isMut: false; @@ -273,8 +268,13 @@ export type Futarchy = { isSigner: false; }, { - name: "staker"; + name: "baseMint"; isMut: false; + isSigner: false; + }, + { + name: "staker"; + isMut: true; isSigner: true; }, { @@ -282,6 +282,11 @@ export type Futarchy = { isMut: false; isSigner: false; }, + { + name: "systemProgram"; + isMut: false; + isSigner: false; + }, { name: "associatedTokenProgram"; isMut: false; @@ -993,16 +998,6 @@ export type Futarchy = { isMut: false; isSigner: false; }, - { - name: "eventAuthority"; - isMut: false; - isSigner: false; - }, - { - name: "program"; - isMut: false; - isSigner: false; - }, ]; args: []; }, @@ -1057,7 +1052,7 @@ export type Futarchy = { }, { name: "squadsMultisigVault"; - isMut: false; + isMut: true; isSigner: false; }, { @@ -1377,7 +1372,7 @@ export type Futarchy = { }, { name: "admin"; - isMut: true; + isMut: false; isSigner: true; }, { @@ -2022,7 +2017,7 @@ export type Futarchy = { "", "Assuming latest observations are as big as possible (u64::MAX * 1e12),", "we can store 18 million seconds worth of observations, which turns out to", - "be ~208 days.", + "be ~213 days.", "", "Assuming that latest observations are 100x smaller than they could theoretically", "be, we can store ~57 years worth of them. Even this is a very", @@ -3503,11 +3498,6 @@ export const IDL: Futarchy = { isMut: false, isSigner: false, }, - { - name: "associatedTokenProgram", - isMut: false, - isSigner: false, - }, { name: "systemProgram", isMut: false, @@ -3562,8 +3552,13 @@ export const IDL: Futarchy = { isSigner: false, }, { - name: "staker", + name: "baseMint", isMut: false, + isSigner: false, + }, + { + name: "staker", + isMut: true, isSigner: true, }, { @@ -3571,6 +3566,11 @@ export const IDL: Futarchy = { isMut: false, isSigner: false, }, + { + name: "systemProgram", + isMut: false, + isSigner: false, + }, { name: "associatedTokenProgram", isMut: false, @@ -4282,16 +4282,6 @@ export const IDL: Futarchy = { isMut: false, isSigner: false, }, - { - name: "eventAuthority", - isMut: false, - isSigner: false, - }, - { - name: "program", - isMut: false, - isSigner: false, - }, ], args: [], }, @@ -4346,7 +4336,7 @@ export const IDL: Futarchy = { }, { name: "squadsMultisigVault", - isMut: false, + isMut: true, isSigner: false, }, { @@ -4666,7 +4656,7 @@ export const IDL: Futarchy = { }, { name: "admin", - isMut: true, + isMut: false, isSigner: true, }, { @@ -5311,7 +5301,7 @@ export const IDL: Futarchy = { "", "Assuming latest observations are as big as possible (u64::MAX * 1e12),", "we can store 18 million seconds worth of observations, which turns out to", - "be ~208 days.", + "be ~213 days.", "", "Assuming that latest observations are 100x smaller than they could theoretically", "be, we can store ~57 years worth of them. Even this is a very", diff --git a/sdk/src/v0.7/types/index.ts b/sdk/src/v0.7/types/index.ts index 62d73d863..d23e656f9 100644 --- a/sdk/src/v0.7/types/index.ts +++ b/sdk/src/v0.7/types/index.ts @@ -92,8 +92,6 @@ export type PerformancePackageV2RewardFunction = IdlTypes["RewardFunction"]; export type PerformancePackageV2PackageStatus = IdlTypes["PackageStatus"]; -export type PerformancePackageV2ProposerType = - IdlTypes["ProposerType"]; export type PerformancePackageV2ThresholdTranche = IdlTypes["ThresholdTranche"]; diff --git a/sdk/src/v0.7/types/launchpad.ts b/sdk/src/v0.7/types/launchpad.ts index 35c57a81d..3950f26cf 100644 --- a/sdk/src/v0.7/types/launchpad.ts +++ b/sdk/src/v0.7/types/launchpad.ts @@ -134,11 +134,6 @@ export type Launchpad = { isMut: true; isSigner: false; }, - { - name: "launchSigner"; - isMut: false; - isSigner: false; - }, { name: "launchQuoteVault"; isMut: true; @@ -479,11 +474,6 @@ export type Launchpad = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -517,7 +507,7 @@ export type Launchpad = { }, { name: "baseMint"; - isMut: true; + isMut: false; isSigner: false; }, { @@ -540,11 +530,6 @@ export type Launchpad = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -617,11 +602,6 @@ export type Launchpad = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -1561,11 +1541,6 @@ export const IDL: Launchpad = { isMut: true, isSigner: false, }, - { - name: "launchSigner", - isMut: false, - isSigner: false, - }, { name: "launchQuoteVault", isMut: true, @@ -1906,11 +1881,6 @@ export const IDL: Launchpad = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, @@ -1944,7 +1914,7 @@ export const IDL: Launchpad = { }, { name: "baseMint", - isMut: true, + isMut: false, isSigner: false, }, { @@ -1967,11 +1937,6 @@ export const IDL: Launchpad = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, @@ -2044,11 +2009,6 @@ export const IDL: Launchpad = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, diff --git a/sdk/src/v0.7/types/launchpad_v7.ts b/sdk/src/v0.7/types/launchpad_v7.ts index 5ef0da28d..fb82a3b6e 100644 --- a/sdk/src/v0.7/types/launchpad_v7.ts +++ b/sdk/src/v0.7/types/launchpad_v7.ts @@ -140,11 +140,6 @@ export type LaunchpadV7 = { isMut: true; isSigner: false; }, - { - name: "launchSigner"; - isMut: false; - isSigner: false; - }, { name: "launchQuoteVault"; isMut: true; @@ -519,11 +514,6 @@ export type LaunchpadV7 = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -557,7 +547,7 @@ export type LaunchpadV7 = { }, { name: "baseMint"; - isMut: true; + isMut: false; isSigner: false; }, { @@ -580,11 +570,6 @@ export type LaunchpadV7 = { isMut: false; isSigner: false; }, - { - name: "systemProgram"; - isMut: false; - isSigner: false; - }, { name: "eventAuthority"; isMut: false; @@ -634,7 +619,7 @@ export type LaunchpadV7 = { }, { name: "launchSigner"; - isMut: true; + isMut: false; isSigner: false; }, { @@ -644,7 +629,7 @@ export type LaunchpadV7 = { }, { name: "baseMint"; - isMut: true; + isMut: false; isSigner: false; }, { @@ -700,7 +685,7 @@ export type LaunchpadV7 = { }, { name: "launchSigner"; - isMut: true; + isMut: false; isSigner: false; }, { @@ -2190,11 +2175,6 @@ export const IDL: LaunchpadV7 = { isMut: true, isSigner: false, }, - { - name: "launchSigner", - isMut: false, - isSigner: false, - }, { name: "launchQuoteVault", isMut: true, @@ -2569,11 +2549,6 @@ export const IDL: LaunchpadV7 = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, @@ -2607,7 +2582,7 @@ export const IDL: LaunchpadV7 = { }, { name: "baseMint", - isMut: true, + isMut: false, isSigner: false, }, { @@ -2630,11 +2605,6 @@ export const IDL: LaunchpadV7 = { isMut: false, isSigner: false, }, - { - name: "systemProgram", - isMut: false, - isSigner: false, - }, { name: "eventAuthority", isMut: false, @@ -2684,7 +2654,7 @@ export const IDL: LaunchpadV7 = { }, { name: "launchSigner", - isMut: true, + isMut: false, isSigner: false, }, { @@ -2694,7 +2664,7 @@ export const IDL: LaunchpadV7 = { }, { name: "baseMint", - isMut: true, + isMut: false, isSigner: false, }, { @@ -2750,7 +2720,7 @@ export const IDL: LaunchpadV7 = { }, { name: "launchSigner", - isMut: true, + isMut: false, isSigner: false, }, { diff --git a/sdk/src/v0.7/types/performance_package_v2.ts b/sdk/src/v0.7/types/performance_package_v2.ts index e246f6498..0956ed645 100644 --- a/sdk/src/v0.7/types/performance_package_v2.ts +++ b/sdk/src/v0.7/types/performance_package_v2.ts @@ -148,11 +148,6 @@ export type PerformancePackageV2 = { isMut: false; isSigner: false; }, - { - name: "associatedTokenProgram"; - isMut: false; - isSigner: false; - }, { name: "mintGovernorProgram"; isMut: false; @@ -341,11 +336,16 @@ export type PerformancePackageV2 = { type: "publicKey"; }, { - name: "proposerType"; - docs: ["Who proposed this change"]; - type: { - defined: "ProposerType"; - }; + name: "proposer"; + docs: ["The proposer's pubkey at proposal time"]; + type: "publicKey"; + }, + { + name: "ppCreatedAtTimestamp"; + docs: [ + "PP's created_at_timestamp at proposal time; used to detect stale CRs after close/recreate", + ]; + type: "i64"; }, { name: "proposedAt"; @@ -452,6 +452,13 @@ export type PerformancePackageV2 = { docs: ["Can't start unlock before this time"]; type: "i64"; }, + { + name: "createdAtTimestamp"; + docs: [ + "Timestamp when this PP was created; used to invalidate stale ChangeRequests", + ]; + type: "i64"; + }, { name: "totalRewardsPaidOut"; docs: ["Cumulative tokens minted to the recipient"]; @@ -577,21 +584,6 @@ export type PerformancePackageV2 = { ]; }; }, - { - name: "ProposerType"; - docs: ["Who proposed the change."]; - type: { - kind: "enum"; - variants: [ - { - name: "Authority"; - }, - { - name: "Recipient"; - }, - ]; - }; - }, { name: "PackageStatus"; docs: ["Lifecycle state for the performance package."]; @@ -669,10 +661,6 @@ export type PerformancePackageV2 = { { name: "CliffLinear"; fields: [ - { - name: "startValue"; - type: "u128"; - }, { name: "cliffValue"; type: "u128"; @@ -865,10 +853,8 @@ export type PerformancePackageV2 = { index: false; }, { - name: "proposerType"; - type: { - defined: "ProposerType"; - }; + name: "proposer"; + type: "publicKey"; index: false; }, { @@ -1074,6 +1060,11 @@ export type PerformancePackageV2 = { name: "MinDurationTooLarge"; msg: "min_duration exceeds maximum allowed (365 days)"; }, + { + code: 6020; + name: "StaleChangeRequest"; + msg: "Change request is stale: PP was recreated or the proposing party has changed"; + }, ]; }; @@ -1227,11 +1218,6 @@ export const IDL: PerformancePackageV2 = { isMut: false, isSigner: false, }, - { - name: "associatedTokenProgram", - isMut: false, - isSigner: false, - }, { name: "mintGovernorProgram", isMut: false, @@ -1420,11 +1406,16 @@ export const IDL: PerformancePackageV2 = { type: "publicKey", }, { - name: "proposerType", - docs: ["Who proposed this change"], - type: { - defined: "ProposerType", - }, + name: "proposer", + docs: ["The proposer's pubkey at proposal time"], + type: "publicKey", + }, + { + name: "ppCreatedAtTimestamp", + docs: [ + "PP's created_at_timestamp at proposal time; used to detect stale CRs after close/recreate", + ], + type: "i64", }, { name: "proposedAt", @@ -1531,6 +1522,13 @@ export const IDL: PerformancePackageV2 = { docs: ["Can't start unlock before this time"], type: "i64", }, + { + name: "createdAtTimestamp", + docs: [ + "Timestamp when this PP was created; used to invalidate stale ChangeRequests", + ], + type: "i64", + }, { name: "totalRewardsPaidOut", docs: ["Cumulative tokens minted to the recipient"], @@ -1656,21 +1654,6 @@ export const IDL: PerformancePackageV2 = { ], }, }, - { - name: "ProposerType", - docs: ["Who proposed the change."], - type: { - kind: "enum", - variants: [ - { - name: "Authority", - }, - { - name: "Recipient", - }, - ], - }, - }, { name: "PackageStatus", docs: ["Lifecycle state for the performance package."], @@ -1748,10 +1731,6 @@ export const IDL: PerformancePackageV2 = { { name: "CliffLinear", fields: [ - { - name: "startValue", - type: "u128", - }, { name: "cliffValue", type: "u128", @@ -1944,10 +1923,8 @@ export const IDL: PerformancePackageV2 = { index: false, }, { - name: "proposerType", - type: { - defined: "ProposerType", - }, + name: "proposer", + type: "publicKey", index: false, }, { @@ -2153,5 +2130,10 @@ export const IDL: PerformancePackageV2 = { name: "MinDurationTooLarge", msg: "min_duration exceeds maximum allowed (365 days)", }, + { + code: 6020, + name: "StaleChangeRequest", + msg: "Change request is stale: PP was recreated or the proposing party has changed", + }, ], }; diff --git a/sdk/src/v0.7/types/price_based_performance_package.ts b/sdk/src/v0.7/types/price_based_performance_package.ts index 70ffba3f9..16224ab94 100644 --- a/sdk/src/v0.7/types/price_based_performance_package.ts +++ b/sdk/src/v0.7/types/price_based_performance_package.ts @@ -341,16 +341,6 @@ export type PriceBasedPerformancePackage = { isMut: false; isSigner: false; }, - { - name: "eventAuthority"; - isMut: false; - isSigner: false; - }, - { - name: "program"; - isMut: false; - isSigner: false; - }, ]; args: []; }, @@ -1322,16 +1312,6 @@ export const IDL: PriceBasedPerformancePackage = { isMut: false, isSigner: false, }, - { - name: "eventAuthority", - isMut: false, - isSigner: false, - }, - { - name: "program", - isMut: false, - isSigner: false, - }, ], args: [], }, diff --git a/tests/bidWall/unit/closeBidWall.test.ts b/tests/bidWall/unit/closeBidWall.test.ts index 5648caa56..d424aba58 100644 --- a/tests/bidWall/unit/closeBidWall.test.ts +++ b/tests/bidWall/unit/closeBidWall.test.ts @@ -182,7 +182,6 @@ export default function suite() { daoTreasury: daoTreasury, baseMint: META, quoteMint: MAINNET_USDC, - payer: this.payer.publicKey, }) .rpc(); @@ -230,7 +229,6 @@ export default function suite() { baseMint: META, feeRecipient: feeRecipient, quoteMint: MAINNET_USDC, - payer: this.payer.publicKey, }) .rpc(); @@ -296,7 +294,6 @@ export default function suite() { baseMint: META, feeRecipient: feeRecipient, quoteMint: MAINNET_USDC, - payer: this.payer.publicKey, }) .rpc(); @@ -362,7 +359,6 @@ export default function suite() { baseMint: META, feeRecipient: feeRecipient, quoteMint: MAINNET_USDC, - payer: this.payer.publicKey, }) .rpc(); @@ -404,7 +400,6 @@ export default function suite() { baseMint: META, feeRecipient: feeRecipient, quoteMint: MAINNET_USDC, - payer: this.payer.publicKey, }) .rpc(); assert.fail("Should have thrown error"); @@ -428,7 +423,6 @@ export default function suite() { baseMint: META, feeRecipient: wrongFeeRecipient, quoteMint: MAINNET_USDC, - payer: this.payer.publicKey, }) .rpc(); assert.fail("Should have thrown error"); diff --git a/tests/futarchy/unit/launchProposal.test.ts b/tests/futarchy/unit/launchProposal.test.ts index 30951fae7..b69b0a1d7 100644 --- a/tests/futarchy/unit/launchProposal.test.ts +++ b/tests/futarchy/unit/launchProposal.test.ts @@ -323,6 +323,73 @@ export default function suite() { ); }); + it("sets proposal duration_in_seconds to DAO's current seconds_per_proposal on launch", async function () { + const THREE_DAYS = 60 * 60 * 24 * 3; // 259200 + const FIVE_DAYS = 60 * 60 * 24 * 5; // 432000 + + // Create DAO with secondsPerProposal = 3 days + const dao = await createDaoWithStakeThreshold(this, META, USDC, new BN(0)); + + // Add liquidity + await this.futarchy + .provideLiquidityIx({ + dao, + baseMint: META, + quoteMint: USDC, + quoteAmount: new BN(100_000 * 10 ** 6), + maxBaseAmount: new BN(100_000 * 10 ** 6), + minLiquidity: new BN(0), + positionAuthority: this.payer.publicKey, + liquidityProvider: this.payer.publicKey, + }) + .preInstructions([ + ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }), + ]) + .rpc(); + + const { proposal, squadsProposal } = await initializeProposal(this, dao); + + // Sponsor the proposal + await this.futarchy + .sponsorProposalIx({ + proposal, + dao, + teamAddress: this.payer.publicKey, + }) + .rpc(); + + // Verify proposal has the original duration (3 days) + const proposalBefore = await this.futarchy.getProposal(proposal); + assert.equal(proposalBefore.durationInSeconds, THREE_DAYS); + + // Directly modify the DAO's secondsPerProposal to 5 days + const daoAccountInfo = await this.banksClient.getAccount(dao); + const coder = this.futarchy.autocrat.coder.accounts; + const daoData = coder.decode("dao", Buffer.from(daoAccountInfo.data)); + daoData.secondsPerProposal = FIVE_DAYS; + const encodedData = await coder.encode("dao", daoData); + // Preserve original account size (may be larger due to InitSpace allocation) + const newData = new Uint8Array(daoAccountInfo.data.length); + newData.set(encodedData, 0); + daoAccountInfo.data = newData; + this.context.setAccount(dao, daoAccountInfo); + + // Launch the proposal + await this.futarchy + .launchProposalIx({ + proposal, + dao, + baseMint: META, + quoteMint: USDC, + squadsProposal, + }) + .rpc(); + + // Verify proposal picked up the new DAO duration + const storedProposal = await this.futarchy.getProposal(proposal); + assert.equal(storedProposal.durationInSeconds, FIVE_DAYS); + }); + it("fails for non-team-sponsored with insufficient stake", async function () { const stakeThreshold = new BN(100 * 10 ** 6); // 100 tokens const dao = await createDaoWithStakeThreshold( diff --git a/tests/performancePackageV2/unit/completeUnlock.test.ts b/tests/performancePackageV2/unit/completeUnlock.test.ts index b22e461ea..1ca99b662 100644 --- a/tests/performancePackageV2/unit/completeUnlock.test.ts +++ b/tests/performancePackageV2/unit/completeUnlock.test.ts @@ -66,7 +66,6 @@ export default function suite() { // Setup reward function where cliff is at current time (immediately earns cliff_amount) // and end is far in future const rewardFunction = createCliffLinearReward({ - startValue: new BN(0), cliffValue: new BN(currentTimestamp), // Cliff at current time endValue: new BN(currentTimestamp + 1000), // End 1000 seconds from now cliffAmount: new BN(100_000_000), // 100 tokens @@ -440,7 +439,6 @@ export default function suite() { // Setup CliffLinear with a time range that spans past and future // cliff at current time, end 1000 seconds in the future const rewardFunction = createCliffLinearReward({ - startValue: new BN(0), cliffValue: new BN(currentTimestamp), // Cliff at current time endValue: new BN(currentTimestamp + 1000), // End 1000 seconds from now cliffAmount: new BN(100_000_000), // 100 tokens at cliff @@ -574,7 +572,6 @@ export default function suite() { // The DAO's twapInitialObservation is ~1000 (from setupDaoForTwapTests) // TWAP of 1000 is 50% into the 500-1500 range → earns ~300 tokens const rewardFunction = createCliffLinearReward({ - startValue: new BN(0), cliffValue: new BN(500), // Cliff at TWAP = 500 endValue: new BN(1500), // End at TWAP = 1500 cliffAmount: new BN(100_000_000), // 100 tokens at cliff diff --git a/tests/performancePackageV2/unit/executeChange.test.ts b/tests/performancePackageV2/unit/executeChange.test.ts index 2745fba09..1450ff583 100644 --- a/tests/performancePackageV2/unit/executeChange.test.ts +++ b/tests/performancePackageV2/unit/executeChange.test.ts @@ -609,6 +609,345 @@ export default function suite() { .then(callbacks[0], callbacks[1]); }); + it("fails with stale CR after authority changes via change_authority", async function () { + const authority = Keypair.generate(); + const recipient = Keypair.generate(); + const newAuthority = Keypair.generate(); + const newRecipient = Keypair.generate(); + + const { performancePackage } = await setupPerformancePackageV2( + this.banksClient, + mintGovernorClient, + ppClient, + this.payer, + { + authority: authority.publicKey, + recipient: recipient.publicKey, + rewardFunction: createCliffLinearReward(), + minUnlockTimestamp: new BN(0), + }, + ); + + // Authority proposes a recipient change + const pdaNonce = 1; + const [changeRequest] = ppClient.getChangeRequestAddr( + performancePackage, + authority.publicKey, + pdaNonce, + ); + + await ppClient + .proposeChangeIx({ + performancePackage, + proposer: authority.publicKey, + payer: this.payer.publicKey, + pdaNonce, + newRecipient: newRecipient.publicKey, + }) + .signers([authority]) + .rpc(); + + // Authority transfers authority to newAuthority + await ppClient + .changeAuthorityIx({ + performancePackage, + authority: authority.publicKey, + newAuthority: newAuthority.publicKey, + }) + .signers([authority]) + .rpc(); + + // Recipient tries to execute the old CR — should fail because authority changed + const callbacks = expectError( + "StaleChangeRequest", + "Should have failed because authority changed since CR was proposed", + ); + + await ppClient + .executeChangeIx({ + performancePackage, + changeRequest, + executor: recipient.publicKey, + rentDestination: this.payer.publicKey, + }) + .signers([recipient]) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + + it("fails with stale CR after recipient changes via execute_change", async function () { + const authority = Keypair.generate(); + const recipient = Keypair.generate(); + const newRecipient = Keypair.generate(); + + const { performancePackage } = await setupPerformancePackageV2( + this.banksClient, + mintGovernorClient, + ppClient, + this.payer, + { + authority: authority.publicKey, + recipient: recipient.publicKey, + rewardFunction: createCliffLinearReward(), + minUnlockTimestamp: new BN(0), + }, + ); + + // Recipient proposes some change (oracle change) + const pdaNonce = 1; + const [staleCR] = ppClient.getChangeRequestAddr( + performancePackage, + recipient.publicKey, + pdaNonce, + ); + + await ppClient + .proposeChangeIx({ + performancePackage, + proposer: recipient.publicKey, + payer: this.payer.publicKey, + pdaNonce, + newOracleReader: { time: {} }, + }) + .signers([recipient]) + .rpc(); + + // Authority proposes a recipient change (separate CR) + const pdaNonce2 = 2; + const [recipientChangeCR] = ppClient.getChangeRequestAddr( + performancePackage, + authority.publicKey, + pdaNonce2, + ); + + await ppClient + .proposeChangeIx({ + performancePackage, + proposer: authority.publicKey, + payer: this.payer.publicKey, + pdaNonce: pdaNonce2, + newRecipient: newRecipient.publicKey, + }) + .signers([authority]) + .rpc(); + + // Recipient executes the recipient change CR + await ppClient + .executeChangeIx({ + performancePackage, + changeRequest: recipientChangeCR, + executor: recipient.publicKey, + rentDestination: this.payer.publicKey, + }) + .signers([recipient]) + .rpc(); + + // Verify recipient changed + const ppAccount = + await ppClient.fetchPerformancePackage(performancePackage); + assert.equal( + ppAccount.recipient.toBase58(), + newRecipient.publicKey.toBase58(), + ); + + // Authority tries to execute the old recipient's CR — should fail + const callbacks = expectError( + "StaleChangeRequest", + "Should have failed because the proposing recipient has changed", + ); + + await ppClient + .executeChangeIx({ + performancePackage, + changeRequest: staleCR, + executor: authority.publicKey, + rentDestination: this.payer.publicKey, + }) + .signers([authority]) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + + it("concurrent CRs from authority and recipient both succeed", async function () { + const authority = Keypair.generate(); + const recipient = Keypair.generate(); + const newRecipient = Keypair.generate(); + + const { performancePackage } = await setupPerformancePackageV2( + this.banksClient, + mintGovernorClient, + ppClient, + this.payer, + { + authority: authority.publicKey, + recipient: recipient.publicKey, + rewardFunction: createCliffLinearReward(), + minUnlockTimestamp: new BN(0), + }, + ); + + // Authority proposes CR1: change reward function + const pdaNonce1 = 1; + const [cr1] = ppClient.getChangeRequestAddr( + performancePackage, + authority.publicKey, + pdaNonce1, + ); + + const newRewardFunction = createThresholdReward([ + { threshold: new BN(100), cumulativeAmount: new BN(100_000_000) }, + ]); + + await ppClient + .proposeChangeIx({ + performancePackage, + proposer: authority.publicKey, + payer: this.payer.publicKey, + pdaNonce: pdaNonce1, + newRewardFunction, + }) + .signers([authority]) + .rpc(); + + // Recipient proposes CR2: change recipient + const pdaNonce2 = 1; + const [cr2] = ppClient.getChangeRequestAddr( + performancePackage, + recipient.publicKey, + pdaNonce2, + ); + + await ppClient + .proposeChangeIx({ + performancePackage, + proposer: recipient.publicKey, + payer: this.payer.publicKey, + pdaNonce: pdaNonce2, + newRecipient: newRecipient.publicKey, + }) + .signers([recipient]) + .rpc(); + + // Recipient executes CR1 (authority proposed) + await ppClient + .executeChangeIx({ + performancePackage, + changeRequest: cr1, + executor: recipient.publicKey, + rentDestination: this.payer.publicKey, + }) + .signers([recipient]) + .rpc(); + + // Authority executes CR2 (recipient proposed) + await ppClient + .executeChangeIx({ + performancePackage, + changeRequest: cr2, + executor: authority.publicKey, + rentDestination: this.payer.publicKey, + }) + .postInstructions([ + ComputeBudgetProgram.setComputeUnitLimit({ units: 200_001 }), + ]) + .signers([authority]) + .rpc(); + + // Verify both changes applied + const ppAccount = + await ppClient.fetchPerformancePackage(performancePackage); + assert.equal( + ppAccount.recipient.toBase58(), + newRecipient.publicKey.toBase58(), + ); + assert.isDefined(ppAccount.rewardFunction.threshold); + }); + + it("fails with stale CR after PP is closed and recreated", async function () { + const authority = Keypair.generate(); + const recipient = Keypair.generate(); + const newRecipient = Keypair.generate(); + + const { performancePackage, createKey, mint, mintGovernor, mintAuthority } = + await setupPerformancePackageV2( + this.banksClient, + mintGovernorClient, + ppClient, + this.payer, + { + authority: authority.publicKey, + recipient: recipient.publicKey, + rewardFunction: createCliffLinearReward(), + minUnlockTimestamp: new BN(0), + }, + ); + + // Authority proposes a recipient change + const pdaNonce = 1; + const [changeRequest] = ppClient.getChangeRequestAddr( + performancePackage, + authority.publicKey, + pdaNonce, + ); + + await ppClient + .proposeChangeIx({ + performancePackage, + proposer: authority.publicKey, + payer: this.payer.publicKey, + pdaNonce, + newRecipient: newRecipient.publicKey, + }) + .signers([authority]) + .rpc(); + + // Close the PP + await ppClient + .closePerformancePackageIx({ + performancePackage, + admin: this.payer.publicKey, + rentDestination: this.payer.publicKey, + }) + .rpc(); + + // Advance time so recreated PP gets a different created_at_timestamp + await this.advanceBySeconds(2); + + // Recreate PP at same address (same createKey → same PDA) + await ppClient + .initializePerformancePackageIx({ + createKey: createKey.publicKey, + mint, + mintGovernor, + mintAuthority, + authority: authority.publicKey, + recipient: recipient.publicKey, + payer: this.payer.publicKey, + oracleReader: { time: {} }, + rewardFunction: createCliffLinearReward(), + minUnlockTimestamp: new BN(0), + }) + .signers([createKey]) + .rpc(); + + // Attempt to execute the old CR — should fail due to timestamp mismatch + const callbacks = expectError( + "StaleChangeRequest", + "Should have failed because CR was created for previous PP incarnation", + ); + + await ppClient + .executeChangeIx({ + performancePackage, + changeRequest, + executor: recipient.publicKey, + rentDestination: this.payer.publicKey, + }) + .signers([recipient]) + .rpc() + .then(callbacks[0], callbacks[1]); + }); + it("fails when reward function change attempted while Unlocking", async function () { const authority = Keypair.generate(); const recipient = Keypair.generate(); diff --git a/tests/performancePackageV2/unit/initializePerformancePackage.test.ts b/tests/performancePackageV2/unit/initializePerformancePackage.test.ts index 29ae7714e..1416ce079 100644 --- a/tests/performancePackageV2/unit/initializePerformancePackage.test.ts +++ b/tests/performancePackageV2/unit/initializePerformancePackage.test.ts @@ -43,7 +43,6 @@ export default function suite() { const minUnlockTimestamp = new BN(1000); const rewardFunction = createCliffLinearReward({ - startValue: new BN(0), cliffValue: new BN(100), endValue: new BN(1000), cliffAmount: new BN(100_000_000), @@ -94,10 +93,10 @@ export default function suite() { assert.isDefined(ppAccount.oracleReader.time); assert.isDefined(ppAccount.rewardFunction.cliffLinear); assert.isDefined(ppAccount.status.locked); + assert.isTrue(ppAccount.createdAtTimestamp.toNumber() > 0); // Verify CliffLinear properties match what we defined const cliffLinear = ppAccount.rewardFunction.cliffLinear; - assert.equal(cliffLinear.startValue.toString(), "0"); assert.equal(cliffLinear.cliffValue.toString(), "100"); assert.equal(cliffLinear.endValue.toString(), "1000"); assert.equal(cliffLinear.cliffAmount.toString(), "100000000"); @@ -250,7 +249,6 @@ export default function suite() { const minDuration = 60; // 60 seconds const oracleReader = createFutarchyTwapOracle({ amm: dao, minDuration }); const rewardFunction = createCliffLinearReward({ - startValue: new BN(0), cliffValue: new BN(100), // TWAP value threshold endValue: new BN(1000), cliffAmount: new BN(100_000_000), // 100 tokens @@ -627,7 +625,6 @@ export default function suite() { // cliff_value (1000) > end_value (500) - invalid const rewardFunction = createCliffLinearReward({ - startValue: new BN(0), cliffValue: new BN(1000), endValue: new BN(500), // Less than cliff! cliffAmount: new BN(100_000_000), @@ -673,7 +670,6 @@ export default function suite() { // cliff_amount (2000) > total_amount (1000) - invalid const rewardFunction = createCliffLinearReward({ - startValue: new BN(0), cliffValue: new BN(100), endValue: new BN(1000), cliffAmount: new BN(2_000_000_000), // More than total! @@ -791,50 +787,4 @@ export default function suite() { assert.isDefined(ppAccount.oracleReader.futarchyTwap); assert.equal(ppAccount.oracleReader.futarchyTwap.minDuration, minDuration); }); - - it("fails with invalid CliffLinear config - start_value > cliff_value", async function () { - const createKey = Keypair.generate(); - const [performancePackage] = getPerformancePackageV2Addr({ - createKey: createKey.publicKey, - }); - - const { mint, mintGovernor, mintAuthority } = - await setupMintGovernorWithAuthority( - this.banksClient, - mintGovernorClient, - this.payer, - performancePackage, - ); - - // start_value (500) > cliff_value (100) - invalid - const rewardFunction = createCliffLinearReward({ - startValue: new BN(500), // Greater than cliff! - cliffValue: new BN(100), - endValue: new BN(1000), - cliffAmount: new BN(100_000_000), - totalAmount: new BN(1_000_000_000), - }); - - const callbacks = expectError( - "InvalidVestingSchedule", - "Should have failed because start_value > cliff_value", - ); - - await ppClient - .initializePerformancePackageIx({ - createKey: createKey.publicKey, - mint, - mintGovernor, - mintAuthority, - authority: this.payer.publicKey, - recipient: this.payer.publicKey, - payer: this.payer.publicKey, - oracleReader: { time: {} }, - rewardFunction, - minUnlockTimestamp: new BN(0), - }) - .signers([createKey]) - .rpc() - .then(callbacks[0], callbacks[1]); - }); } diff --git a/tests/performancePackageV2/unit/proposeChange.test.ts b/tests/performancePackageV2/unit/proposeChange.test.ts index 09ef04aae..44108989b 100644 --- a/tests/performancePackageV2/unit/proposeChange.test.ts +++ b/tests/performancePackageV2/unit/proposeChange.test.ts @@ -67,7 +67,10 @@ export default function suite() { changeRequestAccount.performancePackage.toBase58(), performancePackage.toBase58(), ); - assert.isDefined(changeRequestAccount.proposerType.authority); + assert.equal( + changeRequestAccount.proposer.toBase58(), + authority.publicKey.toBase58(), + ); assert.equal(changeRequestAccount.pdaNonce, pdaNonce); assert.equal( changeRequestAccount.newRecipient.toBase58(), @@ -122,7 +125,10 @@ export default function suite() { const changeRequestAccount = await ppClient.fetchChangeRequest(changeRequest); assert.isNotNull(changeRequestAccount); - assert.isDefined(changeRequestAccount.proposerType.recipient); + assert.equal( + changeRequestAccount.proposer.toBase58(), + recipient.publicKey.toBase58(), + ); }); it("successfully proposes recipient change", async function () { diff --git a/tests/performancePackageV2/utils.ts b/tests/performancePackageV2/utils.ts index 56912b67c..dea04a66c 100644 --- a/tests/performancePackageV2/utils.ts +++ b/tests/performancePackageV2/utils.ts @@ -209,13 +209,11 @@ export async function setupPerformancePackageV2( * Helper to create a CliffLinear reward function */ export function createCliffLinearReward({ - startValue = new BN(0), cliffValue = new BN(100), endValue = new BN(1000), cliffAmount = new BN(100_000_000), // 100 tokens with 6 decimals totalAmount = new BN(1_000_000_000), // 1000 tokens with 6 decimals }: { - startValue?: BN; cliffValue?: BN; endValue?: BN; cliffAmount?: BN; @@ -223,7 +221,6 @@ export function createCliffLinearReward({ } = {}): PerformancePackageV2RewardFunction { return { cliffLinear: { - startValue, cliffValue, endValue, cliffAmount, diff --git a/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts b/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts index e162fecfc..6cc8a6831 100644 --- a/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts +++ b/tests/priceBasedPerformancePackage/unit/initializePerformancePackage.test.ts @@ -185,7 +185,7 @@ export default function () { assert.equal(authorityBalance.toString(), "700000"); // 1000000 - 100000 - 200000 }); - it("should fail if unlock timestamp is in the past", async function () { + it("should succeed even if unlock timestamp is in the past", async function () { const pastCreateKey = Keypair.generate(); // Fund the pastCreateKey with SOL @@ -223,25 +223,33 @@ export default function () { tokenRecipient: recipient.publicKey, }; - try { - const tx = await this.priceBasedPerformancePackage - .initializePerformancePackageIx({ - params, - createKey: pastCreateKey.publicKey, - tokenMint, - grantor: tokenAuthority.publicKey, - }) - .transaction(); + const tx = await this.priceBasedPerformancePackage + .initializePerformancePackageIx({ + params, + createKey: pastCreateKey.publicKey, + tokenMint, + grantorTokenAccount: tokenAccount, + grantor: tokenAuthority.publicKey, + }) + .transaction(); + + tx.recentBlockhash = ( + await this.context.banksClient.getLatestBlockhash() + )[0]; + tx.sign(pastCreateKey, this.payer, tokenAuthority); + await this.banksClient.processTransaction(tx); - tx.recentBlockhash = ( - await this.context.banksClient.getLatestBlockhash() - )[0]; - tx.sign(pastCreateKey, this.payer, tokenAuthority); - await this.banksClient.processTransaction(tx); - assert.fail("Expected transaction to fail"); - } catch (error) { - assert.include(error.message.toLowerCase(), "0x1771"); - } + // Verify the performance package was created successfully + const ppAddr = getPerformancePackageAddr({ + createKey: pastCreateKey.publicKey, + })[0]; + const storedPerformancePackage = + await this.priceBasedPerformancePackage.getPerformancePackage(ppAddr); + assert.equal( + storedPerformancePackage.minUnlockTimestamp.toString(), + params.minUnlockTimestamp.toString(), + ); + assert.exists(storedPerformancePackage.state.locked); }); it("should fail if recipient equals authority", async function () { diff --git a/yarn.lock b/yarn.lock index 7744a0c2f..784a21dbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -975,7 +975,7 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@metadaoproject/futarchy@./sdk": - version "0.7.2-alpha.0" + version "0.7.3-alpha.1" dependencies: "@coral-xyz/anchor" "^0.29.0" "@metaplex-foundation/umi" "^0.9.2"