critical: Bug #93 - Three-layer entry price validation with oracle fallback

Root Cause: quoteAssetAmount/baseAssetAmount division producing garbage entry prices
- Found 6 autosync records with impossible prices (.18, 6.30, 7.11, 116.24 for SOL)
- Drift SDK values can be corrupted during state transitions

Fix Layer 1 (lib/drift/client.ts):
- Added per-asset price range validation (SOL: 0-000, BTC: 0k-00k, ETH: 00-0k)
- Returns null for invalid prices

Fix Layer 2 (lib/trading/sync-helper.ts):
- Added validatedEntryPrice calculation with oracle fallback
- Falls back to Pyth oracle when calculated price is garbage

Fix Layer 3 (lib/trading/sync-helper.ts):
- Trade creation uses validatedEntryPrice in all 4 price fields
- entryPrice, peakPrice, maxFavorablePrice, maxAdversePrice

Documentation:
- Full Bug #93 added to COMMON_PITFALLS.md with code examples
- Quick Reference table updated

Cleaned: 6 garbage autosync records deleted from database
This commit is contained in:
mindesbunister
2026-01-08 19:29:58 +01:00
parent bb2432f3bf
commit f57aa925b8
3 changed files with 235 additions and 13 deletions

View File

@@ -277,6 +277,22 @@ export class DriftService {
// Calculate entry price
const entryPrice = Math.abs(quoteAssetAmount / baseAssetAmount)
// BUG #89 FIX: Validate entry price is realistic for each asset
// Reject garbage entry prices from stale/corrupted position data
const priceValidation: { [key: number]: { min: number; max: number; asset: string } } = {
0: { min: 50, max: 1000, asset: 'SOL' }, // SOL: $50-$1000 range
1: { min: 10000, max: 500000, asset: 'BTC' }, // BTC: $10k-$500k range
2: { min: 500, max: 20000, asset: 'ETH' }, // ETH: $500-$20k range
}
const validation = priceValidation[marketIndex]
if (validation && (entryPrice < validation.min || entryPrice > validation.max)) {
console.error(`❌ INVALID ENTRY PRICE for ${validation.asset}: $${entryPrice.toFixed(2)} (expected $${validation.min}-$${validation.max})`)
console.error(` Raw data: baseAsset=${baseAssetAmount}, quoteAsset=${quoteAssetAmount}`)
console.error(` This indicates corrupted/stale position data - rejecting`)
return null // Reject garbage position data
}
// Get unrealized P&L
const unrealizedPnL = Number(this.user!.getUnrealizedPNL(false, marketIndex)) / 1e6