Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion programs/bid_wall/src/instructions/close_bid_wall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
},
&[&[
Expand Down
2 changes: 2 additions & 0 deletions programs/futarchy/src/instructions/conditional_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ pub struct ConditionalSwap<'info> {
pub amm_fail_quote_vault: Box<Account<'info, TokenAccount>>,

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)]
Expand Down
2 changes: 2 additions & 0 deletions programs/futarchy/src/instructions/spot_swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub struct SpotSwapParams {
pub struct SpotSwap<'info> {
#[account(mut)]
pub dao: Box<Account<'info, Dao>>,
// Intentionally using `token::` instead of `associated_token::`
// DEX integrators may route through non-ATA token accounts.
#[account(
mut,
token::mint = dao.base_mint,
Expand Down
2 changes: 1 addition & 1 deletion programs/price_based_performance_package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions programs/price_based_performance_package/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ pub enum PriceBasedPerformancePackageError {
InvalidTwapLength,
#[msg("Invalid admin")]
InvalidAdmin,
#[msg("Total token amount calculation would overflow")]
TotalTokenAmountOverflow,
}
1 change: 1 addition & 0 deletions programs/price_based_performance_package/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct UnlockCompleted {

#[event]
pub struct ChangeProposed {
pub common: CommonFields,
pub locker: Pubkey,
pub change_request: Pubkey,
pub proposer: Pubkey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct ChangePerformancePackageAuthorityParams {
pub new_performance_package_authority: Pubkey,
}

#[event_cpi]
#[derive(Accounts)]
pub struct ChangePerformancePackageAuthority<'info> {
#[account(mut)]
Expand All @@ -26,6 +27,7 @@ impl<'info> ChangePerformancePackageAuthority<'info> {
let Self {
performance_package,
current_authority: _,
..
} = ctx.accounts;

let ChangePerformancePackageAuthorityParams {
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
};
use anchor_lang::prelude::*;

#[event_cpi]
#[derive(Accounts)]
pub struct ExecuteChange<'info> {
#[account(
Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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::*;

Expand Down Expand Up @@ -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(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -108,7 +106,6 @@ impl ToString for PerformancePackageState {
"Unlocking (start_aggregator: {}, start_timestamp: {})",
start_aggregator, start_timestamp
),
PerformancePackageState::Unlocked => "Unlocked".to_string(),
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions programs/v07_launchpad/src/instructions/fund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion sdk/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
70 changes: 64 additions & 6 deletions sdk/src/v0.7/types/price_based_performance_package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [];
},
Expand All @@ -278,6 +288,16 @@ export type PriceBasedPerformancePackage = {
isMut: false;
isSigner: true;
},
{
name: "eventAuthority";
isMut: false;
isSigner: false;
},
{
name: "program";
isMut: false;
isSigner: false;
},
];
args: [
{
Expand Down Expand Up @@ -656,9 +676,6 @@ export type PriceBasedPerformancePackage = {
},
];
},
{
name: "Unlocked";
},
];
};
},
Expand Down Expand Up @@ -785,6 +802,13 @@ export type PriceBasedPerformancePackage = {
{
name: "ChangeProposed";
fields: [
{
name: "common";
type: {
defined: "CommonFields";
};
index: false;
},
{
name: "locker";
type: "publicKey";
Expand Down Expand Up @@ -942,6 +966,11 @@ export type PriceBasedPerformancePackage = {
name: "InvalidAdmin";
msg: "Invalid admin";
},
{
code: 6014;
name: "TotalTokenAmountOverflow";
msg: "Total token amount calculation would overflow";
},
];
};

Expand Down Expand Up @@ -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: [],
},
Expand All @@ -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: [
{
Expand Down Expand Up @@ -1603,9 +1652,6 @@ export const IDL: PriceBasedPerformancePackage = {
},
],
},
{
name: "Unlocked",
},
],
},
},
Expand Down Expand Up @@ -1732,6 +1778,13 @@ export const IDL: PriceBasedPerformancePackage = {
{
name: "ChangeProposed",
fields: [
{
name: "common",
type: {
defined: "CommonFields",
},
index: false,
},
{
name: "locker",
type: "publicKey",
Expand Down Expand Up @@ -1889,5 +1942,10 @@ export const IDL: PriceBasedPerformancePackage = {
name: "InvalidAdmin",
msg: "Invalid admin",
},
{
code: 6014,
name: "TotalTokenAmountOverflow",
msg: "Total token amount calculation would overflow",
},
],
};
20 changes: 20 additions & 0 deletions tests/launchpad_v7/unit/fund.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
4 changes: 2 additions & 2 deletions tests/launchpad_v7/unit/setFundingRecordApproval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading