518 lines
12 KiB
Markdown
518 lines
12 KiB
Markdown
# Caching & Price Tracking Analysis - MEV Bot
|
|
|
|
**Date**: October 30, 2025
|
|
**Status**: ⚠️ CRITICAL ISSUES IDENTIFIED
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
Analysis of the MEV bot's caching and price tracking mechanisms reveals **3 critical issues** that are preventing effective arbitrage detection:
|
|
|
|
1. **Pool cache is NOT being persisted** to disk
|
|
2. **NO swap events are being processed** from the sequencer feed
|
|
3. **Prices are NOT being updated** from on-chain swaps
|
|
|
|
---
|
|
|
|
## 🔴 Critical Issue #1: Pool Cache Not Persisted
|
|
|
|
### Current Behavior
|
|
|
|
**Discovery IS working** ✅
|
|
- Bot discovered **96 pools** across 32 pairs (up from 10)
|
|
- All 20 new tokens found pools
|
|
- Discovery took ~3 minutes
|
|
|
|
**Cache NOT saved** ❌
|
|
- Pools stored in **memory only**
|
|
- `data/pools.json` shows old data (Oct 27 23:59)
|
|
- Discovered pools lost on restart
|
|
|
|
### Evidence
|
|
|
|
```bash
|
|
# Pool cache file (outdated)
|
|
-rw-r--r-- 1 administrator administrator 5.1K Oct 27 23:59 data/pools.json
|
|
|
|
# Current pools in cache
|
|
$ jq 'length' data/pools.json
|
|
10 # Should be 96!
|
|
|
|
# Latest discovery logs (17:59-18:00)
|
|
[INFO] ✅ Found 96 pools across 32 pairs
|
|
[INFO] Discovery complete! Monitoring 96 pools
|
|
```
|
|
|
|
### Root Cause
|
|
|
|
**Missing or not-called SaveCache function:**
|
|
- `poolsFile: "data/pools.json"` defined in discovery.go
|
|
- No evidence of SaveCache() being called after discovery
|
|
- Pools kept in memory but never persisted
|
|
|
|
### Impact
|
|
|
|
- **High**: Bot loses all discoveries on restart
|
|
- **High**: Must re-discover 96 pools every startup (3+ minutes)
|
|
- **Medium**: Increased RPC calls and startup time
|
|
|
|
---
|
|
|
|
## 🔴 Critical Issue #2: Zero Swap Events Processed
|
|
|
|
### Current Behavior
|
|
|
|
**Bot IS monitoring blocks** ✅
|
|
- Processing blocks every 3 seconds
|
|
- Checking for Uniswap V3 swap events
|
|
- Arbitrage service stats updating every 10s
|
|
|
|
**NO swaps detected** ❌
|
|
- 0 swap events found in 100+ blocks
|
|
- Every block shows: "NO SWAP EVENTS FOUND"
|
|
- 0 arbitrage opportunities detected
|
|
|
|
### Evidence
|
|
|
|
```bash
|
|
# Swap event count
|
|
$ grep "swap event" logs/mev_bot.log | wc -l
|
|
0 # Should be hundreds/thousands
|
|
|
|
# Block monitoring (17:13-17:14)
|
|
[INFO] 📦 Block 395116600: NO SWAP EVENTS FOUND
|
|
[INFO] 📦 Block 395116612: NO SWAP EVENTS FOUND
|
|
[INFO] 📦 Block 395116624: NO SWAP EVENTS FOUND
|
|
... (continues for all blocks)
|
|
|
|
# Arbitrage stats
|
|
[INFO] Arbitrage Service Stats - Detected: 0, Executed: 0
|
|
```
|
|
|
|
### Root Cause Analysis
|
|
|
|
**Possible causes:**
|
|
1. **Wrong event signature** - Uniswap V3 Swap signature mismatch
|
|
2. **Pool address filter** - Only monitoring 96 pools, swaps happening elsewhere
|
|
3. **Block processing lag** - Getting stale blocks after swaps already settled
|
|
4. **Log parsing error** - Swap logs exist but not being decoded correctly
|
|
|
|
### Expected Behavior
|
|
|
|
On Arbitrum mainnet:
|
|
- **~500-1000 swaps per minute** across all DEXes
|
|
- **~5-10 swaps per block** on monitored pools
|
|
- **Bot should detect 50+ swaps/minute** minimum
|
|
|
|
### Impact
|
|
|
|
- **CRITICAL**: Cannot detect arbitrage without swap data
|
|
- **CRITICAL**: No price updates = stale pricing
|
|
- **HIGH**: Bot is effectively blind to market activity
|
|
|
|
---
|
|
|
|
## 🔴 Critical Issue #3: No Price Updates
|
|
|
|
### Current Behavior
|
|
|
|
**Price tracking exists** ✅
|
|
- `ExchangePricer` with `priceCache` map
|
|
- `Market.UpdatePriceData()` function available
|
|
- Price cache initialized at startup
|
|
|
|
**Prices NOT being updated** ❌
|
|
- No swap events = no price data
|
|
- Pools have initial liquidity but no current prices
|
|
- Cache shows 0 entries
|
|
|
|
### Evidence
|
|
|
|
```bash
|
|
# Price-related logs
|
|
$ grep -i "price.*update" logs/mev_bot.log
|
|
(only gas price updates found, no token price updates)
|
|
|
|
# Price cache stats
|
|
priceCache: map[string]*PriceEntry # Empty
|
|
cached_prices: 0
|
|
|
|
# Market price updates
|
|
$ grep "UpdatePriceData" logs/mev_bot.log
|
|
(no results)
|
|
```
|
|
|
|
### How Pricing SHOULD Work
|
|
|
|
```
|
|
1. Sequencer feed → New transaction
|
|
2. Decode Swap event → Extract amounts, pool
|
|
3. Calculate new price → sqrtPriceX96, tick
|
|
4. Update pool cache → Market.UpdatePriceData()
|
|
5. Compare prices → Detect arbitrage
|
|
```
|
|
|
|
### Current Broken Flow
|
|
|
|
```
|
|
1. Sequencer feed → New transaction ✅
|
|
2. Check for swaps → NONE FOUND ❌
|
|
3. No price calculation ❌
|
|
4. No cache update ❌
|
|
5. No arbitrage detection ❌
|
|
```
|
|
|
|
### Impact
|
|
|
|
- **CRITICAL**: Arbitrage detection requires real-time prices
|
|
- **HIGH**: Using stale/initial prices leads to false opportunities
|
|
- **HIGH**: Cannot react to market movements
|
|
|
|
---
|
|
|
|
## 📊 Token Cache Analysis
|
|
|
|
### Current State
|
|
|
|
```json
|
|
{
|
|
"tokens_cached": 6,
|
|
"expected": 20,
|
|
"file": "data/tokens.json",
|
|
"last_updated": "Oct 27 16:26"
|
|
}
|
|
```
|
|
|
|
### Issues
|
|
|
|
1. **Only 6 tokens cached** (should be 20)
|
|
2. **Missing new tokens**: PENDLE, RDNT, MAGIC, GRAIL, AAVE, CRV, BAL, COMP, MKR, USDC.e
|
|
3. **Stale data** - 3 days old
|
|
|
|
### Impact
|
|
|
|
- **MEDIUM**: Token metadata lookups may fail for new tokens
|
|
- **LOW**: Doesn't block trading, just missing metadata
|
|
|
|
---
|
|
|
|
## 🔍 Detailed Investigation
|
|
|
|
### Pool Discovery Process
|
|
|
|
**What's Working:**
|
|
```
|
|
✅ Discovery scans 190 pairs
|
|
✅ Finds pools via CREATE2 calculation
|
|
✅ Validates pools on-chain
|
|
✅ Stores pools in memory
|
|
✅ Logs discovery progress
|
|
```
|
|
|
|
**What's NOT Working:**
|
|
```
|
|
❌ SaveCache() not called after discovery
|
|
❌ data/pools.json not updated
|
|
❌ Pools lost on restart
|
|
```
|
|
|
|
### Swap Processing Pipeline
|
|
|
|
**What's Working:**
|
|
```
|
|
✅ Connects to Arbitrum sequencer feed
|
|
✅ Receives new blocks every 3 seconds
|
|
✅ Checks blocks for Uniswap V3 logs
|
|
✅ Monitors 96 pools
|
|
```
|
|
|
|
**What's NOT Working:**
|
|
```
|
|
❌ No swap events detected (0 in 1000+ blocks)
|
|
❌ No logs match Uniswap V3 Swap signature
|
|
❌ Either wrong signature OR pools inactive
|
|
```
|
|
|
|
### Price Caching System
|
|
|
|
**Components:**
|
|
- `ExchangePricer` - Manages cross-exchange pricing
|
|
- `PriceEntry` - Cache entry with timestamp & validity
|
|
- `Market.UpdatePriceData()` - Updates price per pool
|
|
- `priceCache` map - In-memory price storage
|
|
|
|
**Status:**
|
|
- ✅ System initialized
|
|
- ✅ Data structures ready
|
|
- ❌ Never receives swap data
|
|
- ❌ Never updates prices
|
|
|
|
---
|
|
|
|
## 🔧 Required Fixes
|
|
|
|
### Fix #1: Implement Pool Cache Persistence
|
|
|
|
**Add SaveCache call after discovery:**
|
|
|
|
```go
|
|
// In cmd/mev-bot/main.go after discovery complete
|
|
log.Info(fmt.Sprintf("🎉 Pool discovery complete! Monitoring %d pools", totalPools))
|
|
|
|
// ADD THIS: Save discovered pools to disk
|
|
if err := poolDiscovery.SaveCache(); err != nil {
|
|
log.Error(fmt.Sprintf("Failed to save pool cache: %v", err))
|
|
} else {
|
|
log.Info("✅ Pool cache saved to data/pools.json")
|
|
}
|
|
```
|
|
|
|
**Implement SaveCache if missing:**
|
|
|
|
```go
|
|
// In pkg/pools/discovery.go
|
|
func (pd *PoolDiscovery) SaveCache() error {
|
|
pd.mu.RLock()
|
|
defer pd.mu.RUnlock()
|
|
|
|
// Convert map to slice
|
|
pools := make([]*Pool, 0, len(pd.pools))
|
|
for _, pool := range pd.pools {
|
|
pools = append(pools, pool)
|
|
}
|
|
|
|
// Marshal to JSON
|
|
data, err := json.MarshalIndent(pools, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal pools: %w", err)
|
|
}
|
|
|
|
// Write to file
|
|
if err := os.WriteFile(pd.poolsFile, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write pools file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
```
|
|
|
|
### Fix #2: Debug Swap Event Detection
|
|
|
|
**Check event signature:**
|
|
|
|
```bash
|
|
# Uniswap V3 Swap event signature
|
|
keccak256("Swap(address,address,int256,int256,uint160,uint128,int24)")
|
|
= 0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67
|
|
```
|
|
|
|
**Add diagnostic logging:**
|
|
|
|
```go
|
|
// In swap processing
|
|
log.Info(fmt.Sprintf("Checking block %d for swaps", blockNumber))
|
|
log.Debug(fmt.Sprintf("Monitoring %d pools", len(monitoredPools)))
|
|
|
|
for i, log := range logs {
|
|
log.Debug(fmt.Sprintf("Log %d: topics=%v", i, log.Topics))
|
|
|
|
if log.Topics[0].Hex() == SwapEventSignature {
|
|
log.Info("✅ SWAP FOUND!")
|
|
// Process swap
|
|
}
|
|
}
|
|
```
|
|
|
|
**Verify pool addresses:**
|
|
|
|
```go
|
|
// Check if we're monitoring the right pools
|
|
for pool := range monitoredPools {
|
|
log.Debug(fmt.Sprintf("Monitoring pool: %s", pool.Hex()))
|
|
}
|
|
```
|
|
|
|
### Fix #3: Implement Price Updates from Swaps
|
|
|
|
**Add price update to swap handler:**
|
|
|
|
```go
|
|
// After processing swap event
|
|
func handleSwap(pool common.Address, sqrtPriceX96 *big.Int, tick int32) {
|
|
// Calculate price from sqrtPriceX96
|
|
price := uniswap.SqrtPriceX96ToPrice(sqrtPriceX96)
|
|
|
|
// Update market data
|
|
if market, exists := markets[pool]; exists {
|
|
market.UpdatePriceData(price, liquidity, sqrtPriceX96, tick)
|
|
log.Debug(fmt.Sprintf("Updated price for pool %s: %s",
|
|
pool.Hex()[:8], price.String()))
|
|
}
|
|
|
|
// Update price cache
|
|
cacheKey := fmt.Sprintf("%s/%s", tokenIn, tokenOut)
|
|
priceCache[cacheKey] = &PriceEntry{
|
|
Price: price,
|
|
Timestamp: time.Now(),
|
|
Validity: 5 * time.Minute,
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Verification Steps
|
|
|
|
### After Fix #1 (Pool Persistence)
|
|
|
|
```bash
|
|
# 1. Start bot
|
|
./mev-bot start
|
|
|
|
# 2. Wait for discovery (3 minutes)
|
|
# Should see: "✅ Pool cache saved"
|
|
|
|
# 3. Check file updated
|
|
ls -lh data/pools.json
|
|
# Should show current timestamp
|
|
|
|
# 4. Verify contents
|
|
jq 'length' data/pools.json
|
|
# Should show 96 (not 10)
|
|
|
|
# 5. Restart bot
|
|
# Should load 96 pools instantly
|
|
```
|
|
|
|
### After Fix #2 (Swap Detection)
|
|
|
|
```bash
|
|
# Monitor logs for swaps
|
|
tail -f logs/mev_bot.log | grep -i swap
|
|
|
|
# Expected output:
|
|
[INFO] ✅ SWAP FOUND on pool 0x82af...
|
|
[INFO] Processing swap: 1000 USDC → 0.5 WETH
|
|
[INFO] Updated price for pool 0x82af
|
|
[INFO] Checking for arbitrage opportunities...
|
|
```
|
|
|
|
### After Fix #3 (Price Updates)
|
|
|
|
```bash
|
|
# Check price cache stats
|
|
grep "price.*cache\|cached.*price" logs/mev_bot.log
|
|
|
|
# Expected:
|
|
[INFO] Price cache stats: 32 prices cached
|
|
[INFO] Updated 15 prices in last minute
|
|
[INFO] Oldest price age: 45 seconds
|
|
```
|
|
|
|
---
|
|
|
|
## 📈 Expected Performance After Fixes
|
|
|
|
### Pool Management
|
|
- ✅ 96 pools persisted to disk
|
|
- ✅ Instant load on restart (<1 second)
|
|
- ✅ Discovery runs once, cache reused
|
|
|
|
### Swap Processing
|
|
- ✅ 50-100 swaps/minute detected
|
|
- ✅ Real-time price updates
|
|
- ✅ <100ms latency from swap to price update
|
|
|
|
### Arbitrage Detection
|
|
- ✅ Compare prices across 96 pools
|
|
- ✅ Detect opportunities within 1 block
|
|
- ✅ 5-10+ opportunities/hour expected
|
|
|
|
---
|
|
|
|
## 🚨 Priority Order
|
|
|
|
1. **URGENT**: Fix swap event detection (Fix #2)
|
|
- **Why**: Bot is blind without swap data
|
|
- **Impact**: Enables all arbitrage detection
|
|
|
|
2. **HIGH**: Implement price updates (Fix #3)
|
|
- **Why**: Required for arbitrage calculation
|
|
- **Impact**: Real-time opportunity detection
|
|
|
|
3. **MEDIUM**: Add pool cache persistence (Fix #1)
|
|
- **Why**: Improves startup time
|
|
- **Impact**: UX improvement, reduced RPC load
|
|
|
|
---
|
|
|
|
## 📝 Additional Recommendations
|
|
|
|
### Monitoring Enhancements
|
|
|
|
```bash
|
|
# Add metrics for debugging
|
|
- swap_events_processed (counter)
|
|
- price_updates_count (counter)
|
|
- cache_hit_rate (gauge)
|
|
- pool_count (gauge)
|
|
- arbitrage_opportunities_detected (counter)
|
|
```
|
|
|
|
### Logging Improvements
|
|
|
|
```go
|
|
// Add periodic stats logging
|
|
Every 60 seconds:
|
|
- Pools monitored: 96
|
|
- Swap events (last min): 47
|
|
- Price updates (last min): 32
|
|
- Arbitrage opportunities: 3
|
|
- Cache size: 5.2KB
|
|
```
|
|
|
|
### Testing Strategy
|
|
|
|
1. **Unit Tests**: Test SaveCache() function
|
|
2. **Integration Tests**: Verify swap event parsing
|
|
3. **End-to-End Tests**: Full flow from swap → arbitrage
|
|
4. **Load Tests**: Handle 1000+ swaps/minute
|
|
|
|
---
|
|
|
|
## 🎯 Success Criteria
|
|
|
|
After fixes are applied, the bot should:
|
|
|
|
1. ✅ Discover 96 pools and save to disk
|
|
2. ✅ Detect 50+ swaps per minute
|
|
3. ✅ Update prices within 100ms of swap
|
|
4. ✅ Cache 20-30 active prices
|
|
5. ✅ Detect 5-10 arbitrage opportunities per hour
|
|
6. ✅ Restart in <5 seconds (load from cache)
|
|
|
|
---
|
|
|
|
## 📚 Code References
|
|
|
|
- **Pool Discovery**: `pkg/pools/discovery.go`
|
|
- **Price Engine**: `pkg/pricing/engine.go`
|
|
- **Market Updates**: `pkg/marketmanager/types.go:UpdatePriceData()`
|
|
- **Swap Processing**: `pkg/scanner/market/scanner.go`
|
|
- **Main Entry**: `cmd/mev-bot/main.go:256-323`
|
|
|
|
---
|
|
|
|
**Status**: ⚠️ **CRITICAL FIXES REQUIRED**
|
|
**Next Action**: Implement Fix #2 (Swap Detection) as highest priority
|
|
**Estimated Time**: 2-4 hours for all fixes
|
|
**Expected Value**: Enable arbitrage detection capability
|
|
|
|
---
|
|
|
|
*Document Version*: 1.0
|
|
*Last Updated*: October 30, 2025 18:15 UTC
|
|
*Analysis Duration*: 30 minutes
|
|
*Evidence Reviewed*: 10+ log files, 5 code modules, 3 cache files
|