12 KiB
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:
- Pool cache is NOT being persisted to disk
- NO swap events are being processed from the sequencer feed
- 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.jsonshows old data (Oct 27 23:59)- Discovered pools lost on restart
Evidence
# 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
# 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:
- Wrong event signature - Uniswap V3 Swap signature mismatch
- Pool address filter - Only monitoring 96 pools, swaps happening elsewhere
- Block processing lag - Getting stale blocks after swaps already settled
- 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 ✅
ExchangePricerwithpriceCachemapMarket.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
# 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
{
"tokens_cached": 6,
"expected": 20,
"file": "data/tokens.json",
"last_updated": "Oct 27 16:26"
}
Issues
- Only 6 tokens cached (should be 20)
- Missing new tokens: PENDLE, RDNT, MAGIC, GRAIL, AAVE, CRV, BAL, COMP, MKR, USDC.e
- 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 pricingPriceEntry- Cache entry with timestamp & validityMarket.UpdatePriceData()- Updates price per poolpriceCachemap - 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:
// 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:
// 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:
# Uniswap V3 Swap event signature
keccak256("Swap(address,address,int256,int256,uint160,uint128,int24)")
= 0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67
Add diagnostic logging:
// 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:
// 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:
// 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)
# 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)
# 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)
# 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
-
URGENT: Fix swap event detection (Fix #2)
- Why: Bot is blind without swap data
- Impact: Enables all arbitrage detection
-
HIGH: Implement price updates (Fix #3)
- Why: Required for arbitrage calculation
- Impact: Real-time opportunity detection
-
MEDIUM: Add pool cache persistence (Fix #1)
- Why: Improves startup time
- Impact: UX improvement, reduced RPC load
📝 Additional Recommendations
Monitoring Enhancements
# 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
// 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
- Unit Tests: Test SaveCache() function
- Integration Tests: Verify swap event parsing
- End-to-End Tests: Full flow from swap → arbitrage
- Load Tests: Handle 1000+ swaps/minute
🎯 Success Criteria
After fixes are applied, the bot should:
- ✅ Discover 96 pools and save to disk
- ✅ Detect 50+ swaps per minute
- ✅ Update prices within 100ms of swap
- ✅ Cache 20-30 active prices
- ✅ Detect 5-10 arbitrage opportunities per hour
- ✅ 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