diff --git a/.github/workflows/anchor.yml b/.github/workflows/anchor.yml index 3f8e36133..826e3a7ef 100644 --- a/.github/workflows/anchor.yml +++ b/.github/workflows/anchor.yml @@ -138,7 +138,8 @@ jobs: - uses: heyAyushh/setup-anchor@v4.999 with: anchor-version: 1.0.0 - solana-cli-version: stable + # setup-anchor resolves tags like stable by querying GitHub API for latest release which can fail with 429 errors + solana-cli-version: 3.1.14 - name: Install Surfpool run: curl -sL https://run.surfpool.run/ | bash - name: Display Versions diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 7799128ab..b144d382c 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -222,7 +222,8 @@ jobs: - name: Setup Solana Stable uses: heyAyushh/setup-solana@v5.9 with: - solana-cli-version: stable + # setup-anchor resolves tags like stable by querying GitHub API for latest release which can fail with 429 errors + solana-cli-version: 3.1.14 - name: Build and Test with Stable run: | source build_and_test.sh diff --git a/.github/workflows/pinocchio.yml b/.github/workflows/pinocchio.yml index f63582b14..bb85d0876 100644 --- a/.github/workflows/pinocchio.yml +++ b/.github/workflows/pinocchio.yml @@ -222,7 +222,8 @@ jobs: - name: Setup Solana Stable uses: heyAyushh/setup-solana@v5.9 with: - solana-cli-version: stable + # setup-anchor resolves tags like stable by querying GitHub API for latest release which can fail with 429 errors + solana-cli-version: 3.1.14 - name: Build and Test with Stable run: | source build_and_test.sh diff --git a/.github/workflows/quasar.yml b/.github/workflows/quasar.yml index 25e604b64..916d7d439 100644 --- a/.github/workflows/quasar.yml +++ b/.github/workflows/quasar.yml @@ -197,7 +197,8 @@ jobs: - name: Setup Solana Stable uses: heyAyushh/setup-solana@v5.9 with: - solana-cli-version: stable + # setup-anchor resolves tags like stable by querying GitHub API for latest release which can fail with 429 errors + solana-cli-version: 3.1.14 - name: Install Quasar CLI # Pinned to quasar rev 3d6fb0d8 (the HEAD this migration was written # against, immediately after PRs #195 + #196). The next merged PR diff --git a/.github/workflows/solana-asm.yml b/.github/workflows/solana-asm.yml index ae6615cff..408344466 100644 --- a/.github/workflows/solana-asm.yml +++ b/.github/workflows/solana-asm.yml @@ -103,6 +103,12 @@ jobs: failed_projects: ${{ steps.set-failed.outputs.failed_projects }} steps: - uses: actions/checkout@v4 + # The previous `npm install --global pnpm` step picked up a pnpm release that + # treats ignored build scripts (bufferutil, utf-8-validate) as a hard error and + # fails `pnpm install --frozen-lockfile`. The other workflows (anchor, native, + # pinocchio, typescript) use pnpm/action-setup@v4, which pins a known-good pnpm + # release (10.33.0 at time of writing) that only warns. Match that here. + - uses: pnpm/action-setup@v4 - name: Use Node.js uses: actions/setup-node@v4 with: @@ -180,15 +186,16 @@ jobs: # Make the script executable chmod +x build_and_test.sh - # Install pnpm - npm install --global pnpm + # pnpm is installed by pnpm/action-setup@v4 above. Avoid `npm install --global pnpm` + # here because that resolves to pnpm 10+, which errors on ignored build scripts. # Install sbpf assembler cargo install --git https://github.com/blueshift-gg/sbpf.git - name: Setup Solana Stable uses: heyAyushh/setup-solana@v5.9 with: - solana-cli-version: stable + # setup-anchor resolves tags like stable by querying GitHub API for latest release which can fail with 429 errors + solana-cli-version: 3.1.14 - name: Build and Test with Stable run: | source build_and_test.sh diff --git a/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs b/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs index 3a0e69e1f..43c878d12 100644 --- a/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs +++ b/compression/cnft-burn/quasar/src/instructions/burn_cnft.rs @@ -43,7 +43,12 @@ pub fn handle_burn_cnft(accounts: &mut BurnCnft, data: &[u8], remaining: Remaini ix_data[0..8].copy_from_slice(&BURN_DISCRIMINATOR); ix_data[8..116].copy_from_slice(&data[0..108]); - // Collect remaining accounts (proof nodes) into a stack buffer + // Collect remaining accounts (proof nodes) into a stack buffer. + // + // `remaining.iter()` yields `Result` in newer + // quasar-lang. Reach the inner `AccountView` via the unchecked accessor + // — this CPI only reads proof addresses and views, never touching the + // accounts' data, so the aliasing/borrow invariants are upheld. let placeholder = accounts.system_program.to_account_view().clone(); let mut proof_views: [AccountView; MAX_PROOF_NODES] = core::array::from_fn(|_| placeholder.clone()); @@ -52,7 +57,10 @@ pub fn handle_burn_cnft(accounts: &mut BurnCnft, data: &[u8], remaining: Remaini if proof_count >= MAX_PROOF_NODES { break; } - proof_views[proof_count] = result?; + let account = result?; + // SAFETY: We only read the AccountView's address and pass an immutable + // view to the bubblegum CPI as a proof node; no aliased data access. + proof_views[proof_count] = unsafe { account.as_account_view_unchecked() }.clone(); proof_count += 1; } diff --git a/compression/cnft-vault/quasar/src/instructions/withdraw.rs b/compression/cnft-vault/quasar/src/instructions/withdraw.rs index f3c4ccb8f..82d61bd76 100644 --- a/compression/cnft-vault/quasar/src/instructions/withdraw.rs +++ b/compression/cnft-vault/quasar/src/instructions/withdraw.rs @@ -50,7 +50,12 @@ pub fn handle_withdraw_cnft(accounts: &mut Withdraw, data: &[u8], remaining: Rem let ix_data = build_transfer_data(&data[0..TRANSFER_ARGS_LEN]); - // Collect proof nodes + // Collect proof nodes. + // + // `remaining.iter()` yields `Result` in newer + // quasar-lang. Reach the inner `AccountView` via the unchecked accessor + // — we only read addresses/views to forward to the bubblegum CPI as + // proof nodes; no aliased data access. let placeholder = accounts.system_program.to_account_view().clone(); let mut proof_views: [AccountView; MAX_PROOF_NODES] = core::array::from_fn(|_| placeholder.clone()); @@ -59,7 +64,9 @@ pub fn handle_withdraw_cnft(accounts: &mut Withdraw, data: &[u8], remaining: Rem if proof_count >= MAX_PROOF_NODES { break; } - proof_views[proof_count] = result?; + let account = result?; + // SAFETY: Only reads address and forwards an immutable view to CPI. + proof_views[proof_count] = unsafe { account.as_account_view_unchecked() }.clone(); proof_count += 1; } diff --git a/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs b/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs index fca3df2d8..9d4be6459 100644 --- a/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs +++ b/compression/cnft-vault/quasar/src/instructions/withdraw_two.rs @@ -65,7 +65,12 @@ pub fn handle_withdraw_two_cnfts(accounts: &mut WithdrawTwo, data: &[u8], remain ]; let signer = Signer::from(&seeds as &[Seed]); - // Collect all remaining accounts (proof1 ++ proof2) + // Collect all remaining accounts (proof1 ++ proof2). + // + // `remaining.iter()` yields `Result` in newer + // quasar-lang. Reach the inner `AccountView` via the unchecked accessor + // — we only read addresses/views to forward to the bubblegum CPIs as + // proof nodes; no aliased data access. let placeholder = accounts.system_program.to_account_view().clone(); let mut all_proofs: [AccountView; MAX_PROOF_NODES * 2] = core::array::from_fn(|_| placeholder.clone()); @@ -74,7 +79,9 @@ pub fn handle_withdraw_two_cnfts(accounts: &mut WithdrawTwo, data: &[u8], remain if total_proofs >= MAX_PROOF_NODES * 2 { break; } - all_proofs[total_proofs] = result?; + let account = result?; + // SAFETY: Only reads address and forwards an immutable view to CPI. + all_proofs[total_proofs] = unsafe { account.as_account_view_unchecked() }.clone(); total_proofs += 1; } diff --git a/compression/cutils/quasar/src/instructions/verify.rs b/compression/cutils/quasar/src/instructions/verify.rs index 76184326d..2034d4ec2 100644 --- a/compression/cutils/quasar/src/instructions/verify.rs +++ b/compression/cutils/quasar/src/instructions/verify.rs @@ -55,7 +55,12 @@ pub fn handle_verify(accounts: &mut Verify, data: &[u8], remaining: RemainingAcc ix_data[40..72].copy_from_slice(&leaf_hash); ix_data[72..76].copy_from_slice(&index.to_le_bytes()); - // Collect proof nodes + // Collect proof nodes. + // + // `remaining.iter()` yields `Result` in newer + // quasar-lang. Reach the inner `AccountView` via the unchecked accessor + // — we only read addresses/views to forward to the compression CPI as + // proof nodes; no aliased data access. let placeholder = accounts.compression_program.to_account_view().clone(); let mut proof_views: [AccountView; MAX_PROOF_NODES] = core::array::from_fn(|_| placeholder.clone()); @@ -64,7 +69,9 @@ pub fn handle_verify(accounts: &mut Verify, data: &[u8], remaining: RemainingAcc if proof_count >= MAX_PROOF_NODES { break; } - proof_views[proof_count] = result?; + let account = result?; + // SAFETY: Only reads address and forwards an immutable view to CPI. + proof_views[proof_count] = unsafe { account.as_account_view_unchecked() }.clone(); proof_count += 1; } diff --git a/tokens/pda-mint-authority/quasar/src/lib.rs b/tokens/pda-mint-authority/quasar/src/lib.rs index c819d5f57..650a9ccb8 100644 --- a/tokens/pda-mint-authority/quasar/src/lib.rs +++ b/tokens/pda-mint-authority/quasar/src/lib.rs @@ -90,8 +90,14 @@ pub struct MintTokens { #[account(mut)] pub payer: Signer, /// The PDA mint whose authority is itself. + /// + /// Typed as `InterfaceAccount` rather than `Account` because + /// newer quasar-lang requires `T: Discriminator` when combining `address =` + /// with `Account` (it reads `T::BUMP_OFFSET`). SPL `Mint` doesn't + /// implement `Discriminator`; `InterfaceAccount` takes the generic + /// existing-account verifier path that doesn't need it. #[account(mut, address = MintPda::seeds())] - pub mint: Account, + pub mint: InterfaceAccount, /// Recipient token account (must already exist). #[account(mut)] pub token_account: Account, diff --git a/tokens/token-swap/quasar/src/instructions/deposit_liquidity.rs b/tokens/token-swap/quasar/src/instructions/deposit_liquidity.rs index 5abbc4c92..3fd10c264 100644 --- a/tokens/token-swap/quasar/src/instructions/deposit_liquidity.rs +++ b/tokens/token-swap/quasar/src/instructions/deposit_liquidity.rs @@ -22,8 +22,15 @@ pub struct DepositLiquidity { pub pool_authority: UncheckedAccount, /// Depositor (must be signer to authorise transfers). pub depositor: Signer, + /// LP mint at the LiquidityMintPda. + /// + /// Typed as `InterfaceAccount` rather than `Account` because + /// newer quasar-lang requires `T: Discriminator` when combining `address =` + /// with `Account` (it reads `T::BUMP_OFFSET`). SPL `Mint` doesn't + /// implement `Discriminator`; `InterfaceAccount` takes the generic + /// existing-account verifier path that doesn't need it. #[account(mut, address = LiquidityMintPda::seeds(amm.address(), mint_a.address(), mint_b.address()))] - pub mint_liquidity: Account, + pub mint_liquidity: InterfaceAccount, pub mint_a: Account, pub mint_b: Account, /// Pool's token A vault. diff --git a/tokens/token-swap/quasar/src/instructions/withdraw_liquidity.rs b/tokens/token-swap/quasar/src/instructions/withdraw_liquidity.rs index d9cdff0a3..1b23369e9 100644 --- a/tokens/token-swap/quasar/src/instructions/withdraw_liquidity.rs +++ b/tokens/token-swap/quasar/src/instructions/withdraw_liquidity.rs @@ -18,8 +18,15 @@ pub struct WithdrawLiquidity { #[account(address = PoolAuthorityPda::seeds(amm.address(), mint_a.address(), mint_b.address()))] pub pool_authority: UncheckedAccount, pub depositor: Signer, + /// LP mint at the LiquidityMintPda. + /// + /// Typed as `InterfaceAccount` rather than `Account` because + /// newer quasar-lang requires `T: Discriminator` when combining `address =` + /// with `Account` (it reads `T::BUMP_OFFSET`). SPL `Mint` doesn't + /// implement `Discriminator`; `InterfaceAccount` takes the generic + /// existing-account verifier path that doesn't need it. #[account(mut, address = LiquidityMintPda::seeds(amm.address(), mint_a.address(), mint_b.address()))] - pub mint_liquidity: Account, + pub mint_liquidity: InterfaceAccount, #[account(mut)] pub mint_a: Account, #[account(mut)]