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/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(), }, &[&[ 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/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/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/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/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/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/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(), 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/programs/v07_launchpad/src/instructions/fund.rs b/programs/v07_launchpad/src/instructions/fund.rs index c5749bd55..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>, @@ -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/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/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", 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..fe69a3267 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: [ { @@ -656,9 +676,6 @@ export type PriceBasedPerformancePackage = { }, ]; }, - { - name: "Unlocked"; - }, ]; }; }, @@ -785,6 +802,13 @@ export type PriceBasedPerformancePackage = { { name: "ChangeProposed"; fields: [ + { + name: "common"; + type: { + defined: "CommonFields"; + }; + index: false; + }, { name: "locker"; type: "publicKey"; @@ -942,6 +966,11 @@ export type PriceBasedPerformancePackage = { name: "InvalidAdmin"; msg: "Invalid admin"; }, + { + code: 6014; + name: "TotalTokenAmountOverflow"; + msg: "Total token amount calculation would overflow"; + }, ]; }; @@ -1209,6 +1238,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: [], }, @@ -1225,6 +1264,16 @@ export const IDL: PriceBasedPerformancePackage = { isMut: false, isSigner: true, }, + { + name: "eventAuthority", + isMut: false, + isSigner: false, + }, + { + name: "program", + isMut: false, + isSigner: false, + }, ], args: [ { @@ -1603,9 +1652,6 @@ export const IDL: PriceBasedPerformancePackage = { }, ], }, - { - name: "Unlocked", - }, ], }, }, @@ -1732,6 +1778,13 @@ export const IDL: PriceBasedPerformancePackage = { { name: "ChangeProposed", fields: [ + { + name: "common", + type: { + defined: "CommonFields", + }, + index: false, + }, { name: "locker", type: "publicKey", @@ -1889,5 +1942,10 @@ export const IDL: PriceBasedPerformancePackage = { name: "InvalidAdmin", msg: "Invalid admin", }, + { + code: 6014, + name: "TotalTokenAmountOverflow", + msg: "Total token amount calculation would overflow", + }, ], }; 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 }) 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",