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 2bf4af6a..17d4a5af 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 451f5ca9..7365060a 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 9f036633..f41fd1d3 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 2fdc332a..5806f217 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 7ca1765a..be04d366 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 fd64e761..37797ce5 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 314a6826..c5fa9f59 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 9b10dac1..2c40adb2 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 26708dd8..bba2109d 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 2ab330b0..1f167a30 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 881e5173..618e8697 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 2dcc4319..b70d8865 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 8bfc03df..5777d691 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 a78d770d..1272cc0e 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 7aa78ddf..683cd06f 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 02023dcd..b8223936 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 6e489c67..2fcc8f91 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();