# In genius-actions, SolanaSolver.fillOrderSvm transfers swapped tokens to receiver directly

SolanaSolver.fillOrderSvm sends swapped tokens straight to the order receiver, so the orchestrator's balance check sees zero and the fill reverts, letting receivers keep tokens for free.

- Payout: Contest reward
- Severity: high
- Program: Genius
- Credited: davidjohn
- Disclosed: 2025-09-04
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_genius_jul2025.pdf#3.1.4
- Library page: https://bugbountylibrary.com/r/cantina%3Agenius_jul2025%3A3.1.4
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:solana technique:source-review vuln:access-control vuln:business-logic

**Target:** Solana order-fill solver (fillOrderSvm / getSolanaSwapTx)
**Root cause:** getSolanaSwapTx sets the swap receiver to order.receiver instead of the orchestrator, so the orchestrator's tokenOut ATA balance stays zero when generateTransferTokenTxn reads it and passes previousBalance=0 to FillOrderTokenTransfer.
**Steps:**
1. Call fillOrderSvm with tokenOut != USDC on Solana.
2. getSolanaSwapTx builds a swap quote with from=orchestrator, receiver=order.receiver, so swapped tokens land in the receiver's ATA.
3. generateTransferTokenTxn reads orchestratorTokenOutATA balance, which is 0.
4. It builds FILL_ORDER_TOKEN_TRANSFER with minAmountOut and previousBalance=0.
5. fill_order_token_transfer.rs checks the transfer amount against min_amount_out and reverts because 0 < minAmountOut.
6. The swap already delivered tokens to the receiver, so the receiver keeps them while the fill reverts.
**How found:** Traced the fill flow: noticed getSolanaSwapTx passes receiver (order.receiver) rather than orchestrator as the swap destination, then followed generateTransferTokenTxn reading the orchestrator ATA balance and passing it as previousBalance to FillOrderTokenTransfer, spotting the mismatch with the on-chain min_amount_out check.
**Exploitation:** A user places an order with tokenOut not equal to USDC on Solana. The solver swaps and sends the output directly to order.receiver. The subsequent transfer instruction reverts on the min_amount_out check, but the swap output is already in the receiver's account, so the user gets tokens without paying stablecoin (only the fee).
**Insight:** When a solver both swaps and then transfers, verify the swap destination equals the account the later transfer debits; a balance read of the orchestrator ATA returning 0 is the tell that swap output was routed elsewhere, and the on-chain min_amount_out check then reverts after funds already moved.
