From 28e246c6996937af6b52e50984e4dba62b803615 Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 10 Feb 2026 14:29:16 -0800 Subject: [PATCH 1/9] proper SOL recipient for account closing in close_bid_wall --- programs/bid_wall/src/instructions/close_bid_wall.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/bid_wall/src/instructions/close_bid_wall.rs b/programs/bid_wall/src/instructions/close_bid_wall.rs index 2fac01379..4fcd82bd9 100644 --- a/programs/bid_wall/src/instructions/close_bid_wall.rs +++ b/programs/bid_wall/src/instructions/close_bid_wall.rs @@ -121,7 +121,7 @@ impl CloseBidWall<'_> { ctx.accounts.token_program.to_account_info(), token::CloseAccount { account: ctx.accounts.bid_wall_quote_token_account.to_account_info(), - destination: ctx.accounts.payer.to_account_info(), + destination: ctx.accounts.authority.to_account_info(), authority: ctx.accounts.bid_wall.to_account_info(), }, &[&[ From 813a864823b531e757107c833707add0620b09cc Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 10 Feb 2026 14:35:23 -0800 Subject: [PATCH 2/9] remove unused Unlocked state from price based locker --- programs/price_based_performance_package/README.md | 2 +- .../src/state/performance_package.rs | 3 --- sdk/src/v0.7/types/price_based_performance_package.ts | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/programs/price_based_performance_package/README.md b/programs/price_based_performance_package/README.md index dae4c4ce8..8b8842920 100644 --- a/programs/price_based_performance_package/README.md +++ b/programs/price_based_performance_package/README.md @@ -14,7 +14,7 @@ The program allows users to: ### State - **Locker**: The main account that holds the locked tokens and configuration -- **LockerState**: Enum representing the current state (Locked, Unlocking, Unlocked) +- **LockerState**: Enum representing the current state (Locked, Unlocking) ### Instructions diff --git a/programs/price_based_performance_package/src/state/performance_package.rs b/programs/price_based_performance_package/src/state/performance_package.rs index d8b9f9fdb..f591f2e69 100644 --- a/programs/price_based_performance_package/src/state/performance_package.rs +++ b/programs/price_based_performance_package/src/state/performance_package.rs @@ -93,8 +93,6 @@ pub enum PerformancePackageState { /// The timestamp when unlocking started start_timestamp: i64, }, - /// Tokens have been unlocked and sent to recipient - Unlocked, } impl ToString for PerformancePackageState { @@ -108,7 +106,6 @@ impl ToString for PerformancePackageState { "Unlocking (start_aggregator: {}, start_timestamp: {})", start_aggregator, start_timestamp ), - PerformancePackageState::Unlocked => "Unlocked".to_string(), } } } 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 e72df8588..cd693ec06 100644 --- a/sdk/src/v0.7/types/price_based_performance_package.ts +++ b/sdk/src/v0.7/types/price_based_performance_package.ts @@ -656,9 +656,6 @@ export type PriceBasedPerformancePackage = { }, ]; }, - { - name: "Unlocked"; - }, ]; }; }, @@ -1603,9 +1600,6 @@ export const IDL: PriceBasedPerformancePackage = { }, ], }, - { - name: "Unlocked", - }, ], }, }, From ad0da3870099e8d364735316bfa817a87b7e03bc Mon Sep 17 00:00:00 2001 From: Pileks Date: Wed, 11 Feb 2026 16:32:02 -0800 Subject: [PATCH 3/9] prevent funding for one more second after launch end --- .../v07_launchpad/src/instructions/fund.rs | 2 +- tests/launchpad_v7/unit/fund.test.ts | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/programs/v07_launchpad/src/instructions/fund.rs b/programs/v07_launchpad/src/instructions/fund.rs index c5749bd55..d459d05f0 100644 --- a/programs/v07_launchpad/src/instructions/fund.rs +++ b/programs/v07_launchpad/src/instructions/fund.rs @@ -62,7 +62,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/tests/launchpad_v7/unit/fund.test.ts b/tests/launchpad_v7/unit/fund.test.ts index 7452beb42..2d1e8c7c1 100644 --- a/tests/launchpad_v7/unit/fund.test.ts +++ b/tests/launchpad_v7/unit/fund.test.ts @@ -172,6 +172,26 @@ export default function suite() { ); }); + it("fails to fund the launch at the exact boundary second", async function () { + await launchpadClient + .startLaunchIx({ launch, launchAuthority: launchAuthority.publicKey }) + .signers([launchAuthority]) + .rpc(); + await this.createTokenAccount(META, this.payer.publicKey); + + const fundAmount = new BN(100_000000); // 100 USDC + + // Advance time to exactly the boundary (start + secondsForLaunch) + await this.advanceBySeconds(secondsForLaunch); + + try { + await launchpadClient.fundIx({ launch, amount: fundAmount }).rpc(); + assert.fail("Expected fund instruction to fail at exact boundary"); + } catch (e) { + assert.include(e.message, "LaunchExpired"); + } + }); + it("fails to fund the launch after time expires", async function () { await launchpadClient .startLaunchIx({ launch, launchAuthority: launchAuthority.publicKey }) From 7d7a65dc230d2035d7f1bdb50f453c528b3b18d1 Mon Sep 17 00:00:00 2001 From: Pileks Date: Thu, 12 Feb 2026 16:11:09 -0800 Subject: [PATCH 4/9] change fund to only accept ATA. add comments where TA should be used --- programs/futarchy/src/instructions/conditional_swap.rs | 2 ++ programs/futarchy/src/instructions/spot_swap.rs | 2 ++ programs/v07_launchpad/src/instructions/fund.rs | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/programs/futarchy/src/instructions/conditional_swap.rs b/programs/futarchy/src/instructions/conditional_swap.rs index 82bb34fc5..881786da6 100644 --- a/programs/futarchy/src/instructions/conditional_swap.rs +++ b/programs/futarchy/src/instructions/conditional_swap.rs @@ -37,6 +37,8 @@ pub struct ConditionalSwap<'info> { pub amm_fail_quote_vault: Box>, pub trader: Signer<'info>, + // Intentionally using `token::` instead of `associated_token::` + // DEX integrators may route through non-ATA token accounts. #[account(mut, token::authority = trader)] pub user_input_account: Account<'info, TokenAccount>, #[account(mut)] diff --git a/programs/futarchy/src/instructions/spot_swap.rs b/programs/futarchy/src/instructions/spot_swap.rs index 97b76f905..290e48ba5 100644 --- a/programs/futarchy/src/instructions/spot_swap.rs +++ b/programs/futarchy/src/instructions/spot_swap.rs @@ -12,6 +12,8 @@ pub struct SpotSwapParams { pub struct SpotSwap<'info> { #[account(mut)] pub dao: Box>, + // Intentionally using `token::` instead of `associated_token::` + // DEX integrators may route through non-ATA token accounts. #[account( mut, token::mint = dao.base_mint, diff --git a/programs/v07_launchpad/src/instructions/fund.rs b/programs/v07_launchpad/src/instructions/fund.rs index d459d05f0..6aa55ccdb 100644 --- a/programs/v07_launchpad/src/instructions/fund.rs +++ b/programs/v07_launchpad/src/instructions/fund.rs @@ -36,8 +36,8 @@ pub struct Fund<'info> { #[account( mut, - token::mint = launch.quote_mint, - token::authority = funder + associated_token::mint = launch.quote_mint, + associated_token::authority = funder )] pub funder_quote_account: Account<'info, TokenAccount>, From d82d3cb5427dfccf3de1a68db498f54142fab16c Mon Sep 17 00:00:00 2001 From: Pileks Date: Fri, 13 Feb 2026 12:48:08 -0800 Subject: [PATCH 5/9] add proper event to propose change perf package --- programs/price_based_performance_package/src/events.rs | 1 + .../src/instructions/propose_change.rs | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/programs/price_based_performance_package/src/events.rs b/programs/price_based_performance_package/src/events.rs index 60108f9db..e9288d0de 100644 --- a/programs/price_based_performance_package/src/events.rs +++ b/programs/price_based_performance_package/src/events.rs @@ -45,6 +45,7 @@ pub struct UnlockCompleted { #[event] pub struct ChangeProposed { + pub common: CommonFields, pub locker: Pubkey, pub change_request: Pubkey, pub proposer: Pubkey, diff --git a/programs/price_based_performance_package/src/instructions/propose_change.rs b/programs/price_based_performance_package/src/instructions/propose_change.rs index e873e576a..1c4a5f0b2 100644 --- a/programs/price_based_performance_package/src/instructions/propose_change.rs +++ b/programs/price_based_performance_package/src/instructions/propose_change.rs @@ -1,6 +1,6 @@ use crate::{ - ChangeProposed, ChangeRequest, ChangeType, PerformancePackage, PerformancePackageState, - PriceBasedPerformancePackageError, ProposerType, + ChangeProposed, ChangeRequest, ChangeType, CommonFields, PerformancePackage, + PerformancePackageState, PriceBasedPerformancePackageError, ProposerType, }; use anchor_lang::prelude::*; @@ -92,8 +92,11 @@ impl<'info> ProposeChange<'info> { pda_bump: ctx.bumps.change_request, }); + performance_package.seq_num += 1; + // Emit event emit!(ChangeProposed { + common: CommonFields::new(&clock, performance_package.seq_num), locker: performance_package.key(), change_request: change_request.key(), proposer: proposer.key(), From e100b45a8d83c201c6a46770e66c7edfac857feb Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 17 Feb 2026 20:46:57 +0100 Subject: [PATCH 6/9] proper boundary checks for setting funding records --- CLAUDE.md | 4 +++- .../instructions/set_funding_record_approval.rs | 2 +- .../v0.7/types/price_based_performance_package.ts | 14 ++++++++++++++ .../unit/setFundingRecordApproval.test.ts | 4 ++-- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b0b3d1732..e8f43c709 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -169,11 +169,13 @@ require!(account.status == Status::Active, MyError::InvalidStatus); require!(signer.key() == account.authority, MyError::Unauthorized); ``` +### After Editing Program Code +**Always run `./rebuild.sh` after modifying any Rust code under `programs/`.** This rebuilds all programs, regenerates the SDK types, and lints — ensuring tests run against your latest changes. + ### Adding New Instructions 1. Add instruction to Rust program in `programs/[program]/src/instructions/` 2. Update client methods in SDK (`sdk/src/v0.7/`) 3. Add unit tests in `tests/[program]/unit/` -4. Run `./rebuild.sh` to sync types ### Testing with Bankrun Tests use `solana-bankrun` for deterministic testing without external RPC: diff --git a/programs/v07_launchpad/src/instructions/set_funding_record_approval.rs b/programs/v07_launchpad/src/instructions/set_funding_record_approval.rs index f3ee18c25..fd525cdf6 100644 --- a/programs/v07_launchpad/src/instructions/set_funding_record_approval.rs +++ b/programs/v07_launchpad/src/instructions/set_funding_record_approval.rs @@ -41,7 +41,7 @@ impl SetFundingRecordApproval<'_> { .unwrap() .saturating_add(60 * 60 * 24 * 2); - require_gte!( + require_gt!( two_days_after_close, clock.unix_timestamp, LaunchpadError::FundingRecordApprovalPeriodOver 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 cd693ec06..d58fc8fbc 100644 --- a/sdk/src/v0.7/types/price_based_performance_package.ts +++ b/sdk/src/v0.7/types/price_based_performance_package.ts @@ -782,6 +782,13 @@ export type PriceBasedPerformancePackage = { { name: "ChangeProposed"; fields: [ + { + name: "common"; + type: { + defined: "CommonFields"; + }; + index: false; + }, { name: "locker"; type: "publicKey"; @@ -1726,6 +1733,13 @@ export const IDL: PriceBasedPerformancePackage = { { name: "ChangeProposed", fields: [ + { + name: "common", + type: { + defined: "CommonFields", + }, + index: false, + }, { name: "locker", type: "publicKey", diff --git a/tests/launchpad_v7/unit/setFundingRecordApproval.test.ts b/tests/launchpad_v7/unit/setFundingRecordApproval.test.ts index 3e6a14d03..f96573ca8 100644 --- a/tests/launchpad_v7/unit/setFundingRecordApproval.test.ts +++ b/tests/launchpad_v7/unit/setFundingRecordApproval.test.ts @@ -314,8 +314,8 @@ export default function suite() { await launchpadClient.closeLaunchIx({ launch }).rpc(); - // Advance time by 2 days + 1 second - await this.advanceBySeconds(60 * 60 * 24 * 2 + 1); + // Advance time by exactly 2 days (the boundary) + await this.advanceBySeconds(60 * 60 * 24 * 2); const callbacks = expectError( "FundingRecordApprovalPeriodOver", From 7a93dff3e3cfe71fbf9f3346671fb5f47dcf0419 Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 17 Feb 2026 21:53:10 +0100 Subject: [PATCH 7/9] add emit_cpi where missing in pbpp --- .../change_performance_package_authority.rs | 4 +- .../src/instructions/execute_change.rs | 3 +- .../types/price_based_performance_package.ts | 40 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs b/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs index add2c551c..d2477788c 100644 --- a/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs +++ b/programs/price_based_performance_package/src/instructions/change_performance_package_authority.rs @@ -9,6 +9,7 @@ pub struct ChangePerformancePackageAuthorityParams { pub new_performance_package_authority: Pubkey, } +#[event_cpi] #[derive(Accounts)] pub struct ChangePerformancePackageAuthority<'info> { #[account(mut)] @@ -26,6 +27,7 @@ impl<'info> ChangePerformancePackageAuthority<'info> { let Self { performance_package, current_authority: _, + .. } = ctx.accounts; let ChangePerformancePackageAuthorityParams { @@ -41,7 +43,7 @@ impl<'info> ChangePerformancePackageAuthority<'info> { performance_package.seq_num += 1; // Emit event - emit!(PerformancePackageAuthorityChanged { + emit_cpi!(PerformancePackageAuthorityChanged { common: CommonFields::new(&clock, performance_package.seq_num), locker: performance_package.key(), old_authority, diff --git a/programs/price_based_performance_package/src/instructions/execute_change.rs b/programs/price_based_performance_package/src/instructions/execute_change.rs index 4b860c028..582da768e 100644 --- a/programs/price_based_performance_package/src/instructions/execute_change.rs +++ b/programs/price_based_performance_package/src/instructions/execute_change.rs @@ -4,6 +4,7 @@ use crate::{ }; use anchor_lang::prelude::*; +#[event_cpi] #[derive(Accounts)] pub struct ExecuteChange<'info> { #[account( @@ -74,7 +75,7 @@ impl<'info> ExecuteChange<'info> { performance_package.seq_num += 1; // Emit event let clock = Clock::get()?; - emit!(ChangeExecuted { + emit_cpi!(ChangeExecuted { common: CommonFields::new(&clock, performance_package.seq_num), performance_package: performance_package.key(), change_request: change_request.key(), 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 d58fc8fbc..993143c9c 100644 --- a/sdk/src/v0.7/types/price_based_performance_package.ts +++ b/sdk/src/v0.7/types/price_based_performance_package.ts @@ -262,6 +262,16 @@ export type PriceBasedPerformancePackage = { "The party executing the change (must be opposite of proposer)", ]; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: []; }, @@ -278,6 +288,16 @@ export type PriceBasedPerformancePackage = { isMut: false; isSigner: true; }, + { + name: "eventAuthority"; + isMut: false; + isSigner: false; + }, + { + name: "program"; + isMut: false; + isSigner: false; + }, ]; args: [ { @@ -1213,6 +1233,16 @@ export const IDL: PriceBasedPerformancePackage = { "The party executing the change (must be opposite of proposer)", ], }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [], }, @@ -1229,6 +1259,16 @@ export const IDL: PriceBasedPerformancePackage = { isMut: false, isSigner: true, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [ { From b197b81992529ca48b8d45e671898ff2a2dd01fe Mon Sep 17 00:00:00 2001 From: Pileks Date: Tue, 17 Feb 2026 22:11:34 +0100 Subject: [PATCH 8/9] prevent performance package token amount overflow --- programs/price_based_performance_package/src/error.rs | 2 ++ .../src/instructions/initialize_performance_package.rs | 6 +++++- sdk/src/v0.7/types/price_based_performance_package.ts | 10 ++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/programs/price_based_performance_package/src/error.rs b/programs/price_based_performance_package/src/error.rs index 102cf187a..236eaa2a3 100644 --- a/programs/price_based_performance_package/src/error.rs +++ b/programs/price_based_performance_package/src/error.rs @@ -30,4 +30,6 @@ pub enum PriceBasedPerformancePackageError { InvalidTwapLength, #[msg("Invalid admin")] InvalidAdmin, + #[msg("Total token amount calculation would overflow")] + TotalTokenAmountOverflow, } 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 cfdaa03d4..4aee933ef 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 @@ -123,7 +123,11 @@ impl InitializePerformancePackage<'_> { PriceBasedPerformancePackageError::UnlockTimestampInThePast ); - let total_token_amount = tranches.iter().map(|tranche| tranche.token_amount).sum(); + let total_token_amount = tranches.iter().try_fold(0u64, |acc, tranche| { + acc.checked_add(tranche.token_amount).ok_or(error!( + PriceBasedPerformancePackageError::TotalTokenAmountOverflow + )) + })?; require_gt!(total_token_amount, 0); 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 993143c9c..fe69a3267 100644 --- a/sdk/src/v0.7/types/price_based_performance_package.ts +++ b/sdk/src/v0.7/types/price_based_performance_package.ts @@ -966,6 +966,11 @@ export type PriceBasedPerformancePackage = { name: "InvalidAdmin"; msg: "Invalid admin"; }, + { + code: 6014; + name: "TotalTokenAmountOverflow"; + msg: "Total token amount calculation would overflow"; + }, ]; }; @@ -1937,5 +1942,10 @@ export const IDL: PriceBasedPerformancePackage = { name: "InvalidAdmin", msg: "Invalid admin", }, + { + code: 6014, + name: "TotalTokenAmountOverflow", + msg: "Total token amount calculation would overflow", + }, ], }; From 64b7004dbabd53e7af7ecafe4f7a554159de71e4 Mon Sep 17 00:00:00 2001 From: Pileks Date: Wed, 18 Feb 2026 20:27:51 +0100 Subject: [PATCH 9/9] bump sdk package version --- sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/package.json b/sdk/package.json index 82bdf60b3..f0eabbc54 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@metadaoproject/futarchy", - "version": "0.7.0-alpha.12", + "version": "0.7.0-alpha.13", "type": "module", "main": "dist/index.js", "module": "dist/index.js",