From 4f4907661c1eb68dc6262bc9ce3a95f5152452e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 14:48:31 +0000 Subject: [PATCH 1/2] fix(quasar): use transfer_checked in escrow, token-swap, perpetual-futures Raw .transfer() omits the mint and decimals, so a wrong-mint or wrong-decimals token account would be caught only by downstream balance checks (or not at all). transfer_checked carries the mint and decimals through the CPI, matching the Anchor twins and the lending/quasar program. Each call now passes the mint whose token accounts are being moved and its .decimals(); token conservation and vault invariants are unchanged. Verified with cargo check on all three crates (QuasarSVM tests run in CI). token-fundraiser needs its mint threaded into three instruction structs and is handled separately. --- finance/escrow/quasar/src/instructions/cancel_offer.rs | 4 +++- finance/escrow/quasar/src/instructions/make_offer.rs | 4 +++- finance/escrow/quasar/src/instructions/take_offer.rs | 8 ++++++-- .../quasar/src/instructions/add_liquidity.rs | 4 +++- .../quasar/src/instructions/close_position.rs | 4 +++- .../quasar/src/instructions/collect_fees.rs | 4 +++- .../quasar/src/instructions/liquidate_position.rs | 8 ++++++-- .../quasar/src/instructions/open_position.rs | 4 +++- .../quasar/src/instructions/remove_liquidity.rs | 4 +++- .../quasar/src/instructions/claim_admin_fees.rs | 8 ++++++-- .../quasar/src/instructions/deposit_liquidity.rs | 4 ++-- finance/token-swap/quasar/src/instructions/swap_tokens.rs | 8 ++++---- .../quasar/src/instructions/withdraw_liquidity.rs | 4 ++-- 13 files changed, 47 insertions(+), 21 deletions(-) diff --git a/finance/escrow/quasar/src/instructions/cancel_offer.rs b/finance/escrow/quasar/src/instructions/cancel_offer.rs index 2bf4af6a9..17d4a5afd 100644 --- a/finance/escrow/quasar/src/instructions/cancel_offer.rs +++ b/finance/escrow/quasar/src/instructions/cancel_offer.rs @@ -51,11 +51,13 @@ pub fn handle_withdraw_tokens_and_close_cancel_offer( accounts .token_program - .transfer( + .transfer_checked( &accounts.vault, + &accounts.token_mint_a, &accounts.maker_token_account_a, &accounts.offer, accounts.vault.amount(), + accounts.token_mint_a.decimals(), ) .invoke_signed(&seeds)?; diff --git a/finance/escrow/quasar/src/instructions/make_offer.rs b/finance/escrow/quasar/src/instructions/make_offer.rs index 451f5ca95..7365060ab 100644 --- a/finance/escrow/quasar/src/instructions/make_offer.rs +++ b/finance/escrow/quasar/src/instructions/make_offer.rs @@ -61,11 +61,13 @@ pub fn handle_deposit_tokens( ) -> Result<(), ProgramError> { accounts .token_program - .transfer( + .transfer_checked( &accounts.maker_token_account_a, + &accounts.token_mint_a, &accounts.vault, &accounts.maker, amount, + accounts.token_mint_a.decimals(), ) .invoke() } diff --git a/finance/escrow/quasar/src/instructions/take_offer.rs b/finance/escrow/quasar/src/instructions/take_offer.rs index 9f0366338..f41fd1d3d 100644 --- a/finance/escrow/quasar/src/instructions/take_offer.rs +++ b/finance/escrow/quasar/src/instructions/take_offer.rs @@ -52,11 +52,13 @@ pub fn handle_transfer_tokens( ) -> Result<(), ProgramError> { accounts .token_program - .transfer( + .transfer_checked( &accounts.taker_token_account_b, + &accounts.token_mint_b, &accounts.maker_token_account_b, &accounts.taker, accounts.offer.receive, + accounts.token_mint_b.decimals(), ) .invoke() } @@ -77,11 +79,13 @@ pub fn handle_withdraw_tokens_and_close_take( accounts .token_program - .transfer( + .transfer_checked( &accounts.vault, + &accounts.token_mint_a, &accounts.taker_token_account_a, &accounts.offer, accounts.vault.amount(), + accounts.token_mint_a.decimals(), ) .invoke_signed(&seeds)?; diff --git a/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs b/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs index 2fdc332aa..5806f2174 100644 --- a/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs +++ b/finance/perpetual-futures/quasar/src/instructions/add_liquidity.rs @@ -101,11 +101,13 @@ pub fn handle_add_liquidity( accounts .token_program - .transfer( + .transfer_checked( &accounts.provider_collateral, + &accounts.collateral_mint, &accounts.custody_vault, &accounts.provider, amount, + accounts.collateral_mint.decimals(), ) .invoke()?; diff --git a/finance/perpetual-futures/quasar/src/instructions/close_position.rs b/finance/perpetual-futures/quasar/src/instructions/close_position.rs index 7ca1765a4..be04d3666 100644 --- a/finance/perpetual-futures/quasar/src/instructions/close_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/close_position.rs @@ -135,11 +135,13 @@ pub fn handle_close_position( ]; accounts .token_program - .transfer( + .transfer_checked( &accounts.custody_vault, + &accounts.collateral_mint, &accounts.trader_collateral, &accounts.pool_authority, payout, + accounts.collateral_mint.decimals(), ) .invoke_signed(seeds)?; diff --git a/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs b/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs index fd64e7611..37797ce53 100644 --- a/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs @@ -56,11 +56,13 @@ pub fn handle_collect_fees( ]; accounts .token_program - .transfer( + .transfer_checked( &accounts.custody_vault, + &accounts.collateral_mint, &accounts.authority_collateral, &accounts.pool_authority, amount, + accounts.collateral_mint.decimals(), ) .invoke_signed(seeds)?; diff --git a/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs b/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs index 314a68260..c5fa9f59b 100644 --- a/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/liquidate_position.rs @@ -140,22 +140,26 @@ pub fn handle_liquidate_position( if liquidator_payout > 0 { accounts .token_program - .transfer( + .transfer_checked( &accounts.custody_vault, + &accounts.collateral_mint, &accounts.liquidator_collateral, &accounts.pool_authority, liquidator_payout, + accounts.collateral_mint.decimals(), ) .invoke_signed(seeds)?; } if trader_refund > 0 { accounts .token_program - .transfer( + .transfer_checked( &accounts.custody_vault, + &accounts.collateral_mint, &accounts.trader_collateral, &accounts.pool_authority, trader_refund, + accounts.collateral_mint.decimals(), ) .invoke_signed(seeds)?; } diff --git a/finance/perpetual-futures/quasar/src/instructions/open_position.rs b/finance/perpetual-futures/quasar/src/instructions/open_position.rs index 9b10dac1d..2c40adb25 100644 --- a/finance/perpetual-futures/quasar/src/instructions/open_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/open_position.rs @@ -168,11 +168,13 @@ pub fn handle_open_position( accounts .token_program - .transfer( + .transfer_checked( &accounts.trader_collateral, + &accounts.collateral_mint, &accounts.custody_vault, &accounts.owner, collateral_amount, + accounts.collateral_mint.decimals(), ) .invoke()?; diff --git a/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs b/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs index 26708dd8a..bba2109d6 100644 --- a/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs +++ b/finance/perpetual-futures/quasar/src/instructions/remove_liquidity.rs @@ -120,11 +120,13 @@ pub fn handle_remove_liquidity( ]; accounts .token_program - .transfer( + .transfer_checked( &accounts.custody_vault, + &accounts.collateral_mint, &accounts.provider_collateral, &accounts.pool_authority, amount_out, + accounts.collateral_mint.decimals(), ) .invoke_signed(seeds)?; diff --git a/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs b/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs index 2ab330b07..1f167a304 100644 --- a/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs +++ b/finance/token-swap/quasar/src/instructions/claim_admin_fees.rs @@ -91,11 +91,13 @@ pub fn handle_claim_admin_fees( if owed_a > 0 { accounts .token_program - .transfer( + .transfer_checked( &accounts.pool_a, + &accounts.mint_a, &accounts.admin_token_a, &accounts.pool_authority, owed_a, + accounts.mint_a.decimals(), ) .invoke_signed(seeds)?; } @@ -103,11 +105,13 @@ pub fn handle_claim_admin_fees( if owed_b > 0 { accounts .token_program - .transfer( + .transfer_checked( &accounts.pool_b, + &accounts.mint_b, &accounts.admin_token_b, &accounts.pool_authority, owed_b, + accounts.mint_b.decimals(), ) .invoke_signed(seeds)?; } diff --git a/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs b/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs index 881e51735..618e86972 100644 --- a/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs +++ b/finance/token-swap/quasar/src/instructions/deposit_liquidity.rs @@ -213,12 +213,12 @@ pub fn handle_deposit_liquidity( // Transfer token A to the pool. accounts.token_program - .transfer(&accounts.token_a, &accounts.pool_a, &accounts.depositor, amount_a) + .transfer_checked(&accounts.token_a, &accounts.mint_a, &accounts.pool_a, &accounts.depositor, amount_a, accounts.mint_a.decimals()) .invoke()?; // Transfer token B to the pool. accounts.token_program - .transfer(&accounts.token_b, &accounts.pool_b, &accounts.depositor, amount_b) + .transfer_checked(&accounts.token_b, &accounts.mint_b, &accounts.pool_b, &accounts.depositor, amount_b, accounts.mint_b.decimals()) .invoke()?; // Mint LP tokens to the depositor (signed by pool authority). diff --git a/finance/token-swap/quasar/src/instructions/swap_tokens.rs b/finance/token-swap/quasar/src/instructions/swap_tokens.rs index 2dcc43197..b70d88651 100644 --- a/finance/token-swap/quasar/src/instructions/swap_tokens.rs +++ b/finance/token-swap/quasar/src/instructions/swap_tokens.rs @@ -190,20 +190,20 @@ pub fn handle_swap_tokens( if input_is_token_a { // Trader sends token A to pool. accounts.token_program - .transfer(&accounts.token_a, &accounts.pool_a, &accounts.trader, input) + .transfer_checked(&accounts.token_a, &accounts.mint_a, &accounts.pool_a, &accounts.trader, input, accounts.mint_a.decimals()) .invoke()?; // Pool sends token B to trader (signed). accounts.token_program - .transfer(&accounts.pool_b, &accounts.token_b, &accounts.pool_authority, output) + .transfer_checked(&accounts.pool_b, &accounts.mint_b, &accounts.token_b, &accounts.pool_authority, output, accounts.mint_b.decimals()) .invoke_signed(seeds)?; } else { // Pool sends token A to trader (signed). accounts.token_program - .transfer(&accounts.pool_a, &accounts.token_a, &accounts.pool_authority, output) + .transfer_checked(&accounts.pool_a, &accounts.mint_a, &accounts.token_a, &accounts.pool_authority, output, accounts.mint_a.decimals()) .invoke_signed(seeds)?; // Trader sends token B to pool. accounts.token_program - .transfer(&accounts.token_b, &accounts.pool_b, &accounts.trader, input) + .transfer_checked(&accounts.token_b, &accounts.mint_b, &accounts.pool_b, &accounts.trader, input, accounts.mint_b.decimals()) .invoke()?; } diff --git a/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs b/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs index 8bfc03df1..5777d691a 100644 --- a/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs +++ b/finance/token-swap/quasar/src/instructions/withdraw_liquidity.rs @@ -127,12 +127,12 @@ pub fn handle_withdraw_liquidity( // Transfer token A from pool to depositor. accounts.token_program - .transfer(&accounts.pool_a, &accounts.token_a, &accounts.pool_authority, amount_a) + .transfer_checked(&accounts.pool_a, &accounts.mint_a, &accounts.token_a, &accounts.pool_authority, amount_a, accounts.mint_a.decimals()) .invoke_signed(seeds)?; // Transfer token B from pool to depositor. accounts.token_program - .transfer(&accounts.pool_b, &accounts.token_b, &accounts.pool_authority, amount_b) + .transfer_checked(&accounts.pool_b, &accounts.mint_b, &accounts.token_b, &accounts.pool_authority, amount_b, accounts.mint_b.decimals()) .invoke_signed(seeds)?; // Burn LP tokens. From 701aad48b14be89a12fd83259b836184a3140cb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 16:52:04 +0000 Subject: [PATCH 2/2] fix(quasar): use transfer_checked in token-fundraiser Unlike the other finance programs, token-fundraiser's contribute/refund/ check_contributions instructions had no mint account in scope, so the transfer CPIs could not be checked. Thread the fundraiser's mint through each instruction: add `mint_to_raise: Account` bound to `Fundraiser.mint_to_raise` via has_one, then transfer_checked with its decimals. The generated client picks up the new account automatically; the LiteSVM tests pass it via fixture.mint. Program verified with cargo check (lib); QuasarSVM tests run in CI, which regenerates the client with the added account. --- .../quasar/src/instructions/check_contributions.rs | 9 ++++++++- .../quasar/src/instructions/contribute.rs | 9 ++++++++- .../token-fundraiser/quasar/src/instructions/refund.rs | 9 ++++++++- finance/token-fundraiser/quasar/src/tests.rs | 4 ++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs b/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs index a78d770d1..1272cc0ef 100644 --- a/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs +++ b/finance/token-fundraiser/quasar/src/instructions/check_contributions.rs @@ -13,6 +13,7 @@ pub struct CheckContributionsAccountConstraints { mut, has_one(maker), has_one(vault), + has_one(mint_to_raise), close(dest = maker), address = Fundraiser::seeds(maker.address()), )] @@ -24,6 +25,10 @@ pub struct CheckContributionsAccountConstraints { #[account(mut)] pub maker_ta: Account, + // Bound to fundraiser.mint_to_raise by has_one above; carries the decimals + // that transfer_checked validates against the vault and maker_ta. + pub mint_to_raise: Account, + pub token_program: Program, } @@ -51,11 +56,13 @@ pub fn handle_check_contributions( let vault_amount = accounts.vault.amount(); accounts .token_program - .transfer( + .transfer_checked( &accounts.vault, + &accounts.mint_to_raise, &accounts.maker_ta, &accounts.fundraiser, vault_amount, + accounts.mint_to_raise.decimals(), ) .invoke_signed(&seeds)?; diff --git a/finance/token-fundraiser/quasar/src/instructions/contribute.rs b/finance/token-fundraiser/quasar/src/instructions/contribute.rs index 7aa78ddf7..683cd06fd 100644 --- a/finance/token-fundraiser/quasar/src/instructions/contribute.rs +++ b/finance/token-fundraiser/quasar/src/instructions/contribute.rs @@ -18,6 +18,7 @@ pub struct ContributeAccountConstraints { mut, has_one(maker), has_one(vault), + has_one(mint_to_raise), address = Fundraiser::seeds(maker.address()), )] pub fundraiser: Account, @@ -36,6 +37,10 @@ pub struct ContributeAccountConstraints { #[account(mut)] pub vault: Account, + // Bound to fundraiser.mint_to_raise by has_one above; carries the decimals + // that transfer_checked validates against contributor_ta and vault. + pub mint_to_raise: Account, + pub token_program: Program, pub system_program: Program, @@ -77,11 +82,13 @@ pub fn handle_contribute( accounts .token_program - .transfer( + .transfer_checked( &accounts.contributor_ta, + &accounts.mint_to_raise, &accounts.vault, &accounts.contributor, amount, + accounts.mint_to_raise.decimals(), ) .invoke()?; diff --git a/finance/token-fundraiser/quasar/src/instructions/refund.rs b/finance/token-fundraiser/quasar/src/instructions/refund.rs index 02023dcd6..b8223936b 100644 --- a/finance/token-fundraiser/quasar/src/instructions/refund.rs +++ b/finance/token-fundraiser/quasar/src/instructions/refund.rs @@ -18,6 +18,7 @@ pub struct RefundAccountConstraints { mut, has_one(maker), has_one(vault), + has_one(mint_to_raise), address = Fundraiser::seeds(maker.address()), )] pub fundraiser: Account, @@ -35,6 +36,10 @@ pub struct RefundAccountConstraints { #[account(mut)] pub vault: Account, + // Bound to fundraiser.mint_to_raise by has_one above; carries the decimals + // that transfer_checked validates against the vault and contributor_ta. + pub mint_to_raise: Account, + pub token_program: Program, } @@ -76,11 +81,13 @@ pub fn handle_refund(accounts: &mut RefundAccountConstraints, bumps: &RefundAcco accounts .token_program - .transfer( + .transfer_checked( &accounts.vault, + &accounts.mint_to_raise, &accounts.contributor_ta, &accounts.fundraiser, refund_amount, + accounts.mint_to_raise.decimals(), ) .invoke_signed(&seeds)?; diff --git a/finance/token-fundraiser/quasar/src/tests.rs b/finance/token-fundraiser/quasar/src/tests.rs index 6e489c674..2fcc8f916 100644 --- a/finance/token-fundraiser/quasar/src/tests.rs +++ b/finance/token-fundraiser/quasar/src/tests.rs @@ -227,6 +227,7 @@ fn contribute_instruction(fixture: &Fixture, amount: u64) -> Instruction { contributor_account: fixture.contributor_account, contributor_ta: fixture.contributor_ta, vault: fixture.vault, + mint_to_raise: fixture.mint, token_program: quasar_svm::SPL_TOKEN_PROGRAM_ID, system_program: quasar_svm::system_program::ID, amount, @@ -266,6 +267,7 @@ fn refund_instruction(fixture: &Fixture) -> Instruction { contributor_account: fixture.contributor_account, contributor_ta: fixture.contributor_ta, vault: fixture.vault, + mint_to_raise: fixture.mint, token_program: quasar_svm::SPL_TOKEN_PROGRAM_ID, } .into() @@ -520,6 +522,7 @@ fn test_check_contributions_pays_maker_when_target_met() { fundraiser: fixture.fundraiser, vault: fixture.vault, maker_ta, + mint_to_raise: fixture.mint, token_program: quasar_svm::SPL_TOKEN_PROGRAM_ID, } .into(); @@ -547,6 +550,7 @@ fn test_check_contributions_rejected_below_target() { fundraiser: fixture.fundraiser, vault: fixture.vault, maker_ta, + mint_to_raise: fixture.mint, token_program: quasar_svm::SPL_TOKEN_PROGRAM_ID, } .into();