# Missing lamports accountability in shared_escrow can cause unfair selling of NFTs

Shared escrow tracks no lamport balance, so an attacker tops up a dormant pool's escrow to force an overpriced NFT sale and drains it.

- Payout: Contest reward
- Severity: medium
- Program: Tensor
- Credited: m4rio
- Disclosed: 2025-01-31
- Write-up quality: 7/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_tensor_nov2024.pdf#3.2.6
- Library page: https://bugbountylibrary.com/r/cantina%3Atensor_nov2024%3A3.2.6
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:medium tech:solana technique:source-review vuln:business-logic vuln:oracle-manipulation

**Target:** Solana Trade pool / shared escrow (MarginAccount) program
**Root cause:** Shared escrow (MarginAccount) has no accounting variable for lamports; withdrawals use raw transfer_lamports_from_pda against current_price without verifying the escrow's tracked balance, unlike pools which maintain pool.amount.
**Steps:**
1. Alice creates Pool 1 (buy Mad Lads @5 SOL) and Pool 2 (buy SMG Gen2 @8 SOL), both sharing one escrow funded with 8 SOL.
2. Mad Lads order fills, escrow drops to 3 SOL; SMG Gen2 market price falls to 1 SOL but order still bids 8 SOL.
3. Attacker sends 5 SOL directly to the escrow account (raw lamport transfer, bypassing process_deposit_margin_account).
4. Attacker sells SMG Gen2 to Pool 2; pay_seller_fees invokes WithdrawMarginAccountCpiTamm with lamports=current_price=8 SOL.
5. Raw transfer_lamports_from_pda succeeds since balance is now 8 SOL; attacker nets ~7.8 SOL, Alice paid 3 SOL for a 1 SOL NFT.
**How found:** Code review of pay_seller_fees: noticed escrow withdrawal uses raw transfer_lamports_from_pda with current_price and no balance accounting, then contrasted with pools.rs which maintains pool.amount via update_pool_accounting (checked_add/checked_sub) — the asymmetry revealed the missing accountability.
**Exploitation:** Attacker monitors dormant shared-escrow pools whose bid exceeds market price but whose escrow balance is below the bid. They top up the escrow with a direct lamport transfer to meet current_price, then sell the NFT into the pool; the withdrawal pays out the full bid, attacker recovers their top-up plus profit, victim pool loses the difference.
**Insight:** When a protocol maintains an accounting variable for one custody path (pool.amount) but uses raw token/lamport transfers for a parallel path (shared escrow), the unaccounted path can be inflated externally to satisfy price checks; diff the two paths for missing checked_add/checked_sub.
