Files
mev-beta/docs/MEV_BOT_COMPREHENSIVE_AUDIT_20251104.md

417 lines
15 KiB
Markdown

# MEV Bot Profitability Audit - Critical Blockers Identified
**Date:** November 4, 2025
**Status:** ZERO EXECUTIONS - 100% Blocker Rate
**Executive Summary:** The MEV bot detects opportunities but fails to execute ANY arbitrage trades. Analysis reveals 10 critical blockers preventing profitability.
---
## CRITICAL FINDINGS
### Primary Issue: Zero Execution Rate
- **Detected Opportunities:** Yes (multiple per minute)
- **Executed Opportunities:** 0 (ZERO - 100% failure rate)
- **Success Rate:** 0.00%
- **Net Profit:** 0.000000 ETH (no successful trades)
From logs:
```
2025/11/02 15:22:38 [INFO] Arbitrage Service Stats - Detected: 0, Executed: 0, Successful: 0, Success Rate: 0.00%, Total Profit: 0.000000 ETH
```
All detected opportunities show: `isExecutable:false rejectReason:negative profit after gas and slippage costs`
---
## RANKED BLOCKERS BY IMPACT (Most Critical First)
### BLOCKER #1: Zero Amount Detection - CATASTROPHIC
**Impact:** Blocks 95%+ of all opportunity attempts
**Location:** `pkg/profitcalc/profit_calc.go` lines 104-134
**Problem:**
```go
// Lines 108-118: Rejects all zero/nil amounts
if amountIn == nil || amountOut == nil || amountIn.Sign() <= 0 || amountOut.Sign() <= 0 {
opportunity.IsExecutable = false
opportunity.RejectReason = "invalid swap amounts (nil or zero)"
opportunity.Confidence = 0.0
return opportunity
}
// Lines 121-134: Rejects dust amounts below 0.0001 ETH
if amountIn.Cmp(minAmount) < 0 || amountOut.Cmp(minAmount) < 0 {
opportunity.IsExecutable = false
opportunity.RejectReason = "dust amounts below threshold..."
return opportunity
}
```
**Log Evidence:**
```
Amount In: 0.000000 tokens
Amount Out: 0.000000 tokens
rejectReason:negative profit after gas and slippage costs
```
**Root Cause:** Token amount extraction from swap events is broken. The `AnalyzeSwapOpportunity` method receives ZERO amounts even when swaps are clearly happening (e.g., "Amount Out: 1611.982004 tokens" logged but marked as invalid).
**Data Flow Issue:**
1. Swap events are parsed → amounts extracted wrong
2. Amounts are zero or dust when passed to `AnalyzeSwapOpportunity`
3. Opportunity immediately rejected
4. Never reaches execution logic
**Why Not Fixed:** The token decimals or amount parsing is broken in the event parser (`pkg/arbitrum/parser/core.go` or `pkg/events/parser.go`). Amounts are being lost or miscalculated during extraction.
---
### BLOCKER #2: Token Graph Empty - CRITICAL
**Impact:** No arbitrage paths can be found
**Location:** `pkg/arbitrage/multihop.go` lines 167-173
**Problem:**
```go
adjacent := mhs.tokenGraph.GetAdjacentTokens(startToken)
if len(adjacent) == 0 {
mhs.logger.Warn(fmt.Sprintf("⚠️ Start token %s has no adjacent tokens in graph!", startToken.Hex()))
}
```
**Analysis:** The token graph is manually populated with hardcoded pool addresses (lines 522-594) that:
1. Use PLACEHOLDER liquidity values (all set to `uint256.NewInt(1000000000000000000)`)
2. Placeholder `SqrtPriceX96` values (never updated from actual pools)
3. Never connect to the actual Arbitrum mainnet state
4. Have LastUpdated = time.Zero (fails `isPoolUsable()` check at line 703)
**Why Not Fixed:** Pool discovery is DISABLED intentionally:
```go
// From cmd/mev-bot/main.go line 15:
// "github.com/fraktal/mev-beta/internal/tokens" // Not used - pool discovery disabled
```
The system:
- Loads 314 pools from cache (`logs: Loaded 314 pools from cache`)
- But NEVER adds them to the token graph used for arbitrage scanning
- Token graph is manually populated with 8 hardcoded pools (all with stale data)
- No connection between pool cache and token graph
---
### BLOCKER #3: Batch Fetcher Contract Call Reverts - HIGH
**Impact:** Cannot fetch pool state data for ANY pool
**Location:** `pkg/scanner/market/scanner.go` line 139-142
**Problem:**
```
2025/11/02 15:22:49 [WARN] Failed to fetch batch 0-1: batch fetch V3 data failed: execution reverted
(repeated 25+ times)
```
**Root Cause:** The DataFetcher contract call reverts on Arbitrum:
- Contract address: `0xC6BD82306943c0F3104296a46113ca0863723cBD`
- Method: `batchFetchV3Data()` (fixed from broken `batchFetchAllData`)
- Error: execution reverted (likely contract is not deployed or wrong address)
**Impact Chain:**
1. Cannot fetch real pool liquidity
2. Falls back to placeholder values
3. Profit calculations use placeholder data
4. All calculations are mathematically invalid
From logs:
```go
// pkg/scanner/market/scanner.go lines 145-150:
dataFetcherAddrStr := os.Getenv("CONTRACT_DATA_FETCHER")
if dataFetcherAddrStr == "" {
dataFetcherAddrStr = "0x42105682F819891698E76cfE6897F10b75f8aabc" // Fallback
}
```
The fallback address (0x421056...) doesn't match the one in config (0xC6BD82...). Contract may not be deployed at this address.
---
### BLOCKER #4: Profit Margin Calculation Invalid - HIGH
**Impact:** All profitable opportunities marked as unprofitable
**Location:** `pkg/profitcalc/profit_calc.go` lines 261-294
**Problem:**
```
profitMargin:-4.466551104702248e-09 (EXTREME NEGATIVE VALUE)
profitMargin:-38365.84743877249 (EXTREME NEGATIVE VALUE)
```
**Log Evidence from actual opportunities:**
```
rejectReason:negative profit after gas and slippage costs
estimatedProfitETH:0.000000
gasCostETH:0.000007
netProfitETH:-0.000007
profitMargin:-330178.9776420681
```
**Root Cause:** When amounts are zero or near-zero:
1. All calculations use zero amounts
2. Net profit = 0 - 0 - 0.000007 = NEGATIVE
3. Profit margin = negative / 0 = UNDEFINED EXTREME VALUE
4. Circuit breaker rejects it
**Mathematical Issue:**
- Line 264: `profitMargin := new(big.Float).Quo(netProfit, amountOut)`
- When amountOut is near-zero, Quo operation produces extreme values
- Lines 270-283: Bounds checking is BACKWARDS:
```go
if profitMarginFloat > 1.0 { // reject if > 100%
opportunity.IsExecutable = false
} else if profitMarginFloat < -1.0 { // reject if < -100%
opportunity.IsExecutable = false
}
```
The check rejects BOTH extreme values (correct) BUT the logic doesn't prevent -330,000 from being calculated
---
### BLOCKER #5: Unknown Token Filtering - MEDIUM-HIGH
**Impact:** 95% of tokens cannot be priced
**Location:** `pkg/profitcalc/profit_calc.go` lines 152-161
**Problem:**
```go
if arbitrageRoute == nil {
opportunity.IsExecutable = false
opportunity.RejectReason = fmt.Sprintf("unknown token - cannot price %s or %s", tokenA.Hex()[:10], tokenB.Hex()[:10])
opportunity.Confidence = 0.05 // Lower confidence for unknown token
return opportunity
}
```
**Analysis:** The PriceFeed doesn't have pricing for most Arbitrum tokens:
- Only 6 tokens cached: `logs: Loaded 6 tokens from cache`
- Arbitrum has 100+ tradeable tokens
- Any token pair missing from PriceFeed = immediate rejection
- Falls back to simplified spread calculation (~30 bps, unrealistic)
**Impact:**
- Cannot determine actual profitability
- Uses fallback calculation: `profit = amountOut * 0.003 - amountIn * 0.003`
- This is mathematically incorrect for arbitrage
---
### BLOCKER #6: Execution Code Path Unreachable - CRITICAL
**Impact:** Even if opportunities were valid, they wouldn't execute
**Location:** `pkg/arbitrage/service.go` (ExecuteArbitrage method)
**Problem:**
The arbitrage execution chain is:
1. Market scanner detects opportunity → `SetOpportunityForwarder` routes to MultiHopScanner
2. MultiHopScanner.ScanForArbitrage finds paths → returns paths
3. **DEAD END:** Nobody calls the executor with the paths
From logs and code review:
- No evidence of `arb.Executor.Execute()` or `arb.FlashExecutor.Execute()` being called
- The opportunities are detected but never forwarded to actual execution
- `ExecuteArbitrage` method in service.go exists but is never invoked
**Missing Link:** The opportunity forwarder interface is set but:
```go
// pkg/scanner/market/scanner.go line 86-88:
func (s *MarketScanner) SetOpportunityForwarder(forwarder OpportunityForwarder) {
s.opportunityForwarder = forwarder
s.logger.Info("✅ Opportunity forwarder set - will route to multi-hop scanner")
}
```
But nothing actually calls `s.opportunityForwarder.ExecuteArbitrage()` with valid opportunities.
---
### BLOCKER #7: Minimum Profit Threshold Too High - MEDIUM
**Impact:** Rejects legitimate opportunities
**Location:** `pkg/arbitrage/detection_engine.go` line 188-190
**Problem:**
```go
engine.config.MinProfitThreshold, _ = engine.decimalConverter.FromString("0.001", 18, "ETH")
// = 0.001 ETH minimum (~$2 at current prices)
```
**Analysis:** The minimum is actually reasonable (0.001 ETH = ~$2), BUT:
1. Gas cost on Arbitrum = 100-300k gas @ 0.1-0.5 gwei = $0.0002-0.001
2. Minimum 0.5% spread opportunity (across DEXs) = rare
3. With ZERO amounts being detected, threshold becomes irrelevant
**Sub-blocker:** Configuration also has:
```yaml
# config/arbitrum_production.yaml line 263:
min_profit_wei: 1000000000000000 # $2.00 minimum profit
min_roi_percent: 0.05 # Minimum 0.05% ROI
min_confidence_score: 0.6 # Minimum 60% confidence
```
But all detected opportunities have `confidence: 0.1` (10%), so they fail this check.
---
### BLOCKER #8: RPC BatchFetch Contract Wrong/Not Deployed - MEDIUM
**Impact:** Cannot fetch pool state in batches (heavy RPC usage)
**Location:** `pkg/datafetcher/batch_fetcher.go`
**Problem:**
- Config points to `0xC6BD82306943c0F3104296a46113ca0863723cBD`
- Code fallback: `0x42105682F819891698E76cfE6897F10b75f8aabc`
- Neither address works (contract reverts or doesn't exist)
- System falls back to individual RPC calls for each pool
**Impact:**
- RPC rate limits hit immediately
- Cannot fetch data for 314 cached pools
- System stalls on rate limiting
---
### BLOCKER #9: No Live Execution Framework Integration - CRITICAL
**Impact:** Flash loan executor never triggers
**Location:** `pkg/arbitrage/flash_executor.go` and `pkg/arbitrage/executor.go`
**Problem:**
Flash executors are initialized but NEVER called:
```go
// pkg/arbitrage/service.go line 42-43:
flashExecutor *FlashSwapExecutor
liveFramework *LiveExecutionFramework
```
Both are initialized in `NewArbitrageService()` but:
- No goroutines call their Execute methods
- No event listeners forward opportunities to them
- They sit dormant while opportunities pass by
**Missing:** The actual execution loop that should:
1. Listen for valid opportunities
2. Call `flashExecutor.ExecuteFlashSwap()`
3. Monitor transaction result
4. Track profit/loss
Instead, the code only logs opportunities and never executes.
---
### BLOCKER #10: Event Parser Extracts Zero Amounts - HIGH
**Impact:** All swap events show 0 token amounts
**Location:** `pkg/arbitrum/parser/core.go` or `pkg/events/parser.go`
**Problem:**
Swap events are being parsed with ZERO amounts when they contain data:
```
Log entry shows: "Amount Out: 1611.982004 tokens"
But parsed as: amount0Out:0, amount1Out:0
```
**Evidence from logs:**
```
Amount In: 0.000000 tokens
Amount Out: 0.000000 tokens
```
Yet the same transaction logs show real amounts being swapped. The parser is either:
1. Extracting wrong event fields
2. Not handling token decimals correctly
3. Using wrong event signature for Swap events
4. Losing data during amount sign conversion
This is the FIRST failure point in the entire pipeline.
---
## SECONDARY ISSUES
### Issue A: Pool Blacklist Prevents Scanning
- Valid pools are being blacklisted due to transient RPC errors
- Once blacklisted, never attempted again
- System self-limits to fewer and fewer pools
### Issue B: Configuration Mismatch
- Multiple min profit thresholds at different levels
- Different enabled/disabled features in different configs
- Some features marked as DISABLED in code but ENABLED in config
### Issue C: No Actual Wallet Funding Check
- Bot generates keys but never verifies wallet has funds
- Tries to execute trades with 0 balance wallet
- Transactions fail but error handling swallows the message
### Issue D: Rate Limiting Too Aggressive
- Max 5 concurrent RPC calls (`config/arbitrum_production.yaml` line 336)
- 20 requests/second on growth plan (very conservative)
- System rate limits itself before hitting Chainstack limits
---
## ROOT CAUSE ANALYSIS
The system has ARCHITECTURAL SEPARATION:
1. **Detection Pipeline** (WORKING): Finds opportunities ✅
2. **Data Fetch Pipeline** (BROKEN): Cannot get pool state ❌
3. **Profit Calculation** (BROKEN): Uses invalid amounts ❌
4. **Execution Pipeline** (DISCONNECTED): Never triggered ❌
**Why No Executions:**
```
Event Parsed → Amounts = ZERO → Profit Rejected → Execution Never Attempted
ROOT CAUSE
```
The token amount extraction is broken at the event parser level. Everything downstream (profit calc, execution decisions) becomes irrelevant because the input data is invalid.
---
## RECOMMENDATIONS (Priority Order)
### P0: Fix Event Parser Amount Extraction
- Review `pkg/events/parser.go` - validate Swap event parsing
- Check token decimal handling in amount conversion
- Verify event signature matching (Uniswap V3 Swap vs V2)
- Test with real swap transactions to verify amount extraction
### P1: Connect Token Graph to Pool Cache
- Link the 314 cached pools to the token graph
- Implement pool discovery loop that updates token graph
- Verify graph has minimum 20+ tokens with 50+ pools
### P2: Fix Batch Fetcher Contract
- Deploy actual DataFetcher contract or find correct address
- Test `batchFetchV3Data()` call on Arbitrum
- Implement fallback to individual RPC calls with proper caching
### P3: Connect Execution Pipeline
- Add event listener that forwards VALID opportunities to executor
- Implement execution goroutine
- Add transaction monitoring and result tracking
### P4: Verify Wallet Funding
- Check wallet balance before any execution attempt
- Require minimum balance (0.05 ETH) for flash loan execution
- Add pre-flight validation
---
## TESTING CHECKLIST
- [ ] Swap event parser extracts amounts correctly
- [ ] Profit calculator receives non-zero amounts
- [ ] 50+ opportunities marked as `isExecutable:true`
- [ ] TokenGraph has 20+ tokens, 50+ pool connections
- [ ] DataFetcher contract calls succeed
- [ ] First profitable arbitrage executes successfully
- [ ] Transaction confirmed on-chain
- [ ] Profit > gas cost
---
## ESTIMATED FIX EFFORT
| Blocker | Effort | Time |
|---------|--------|------|
| #1 - Event Parser Fix | 2-4 hours | Debug + unit test + integration test |
| #2 - Token Graph Link | 1-2 hours | Wire up pool cache to graph |
| #3 - Batch Fetcher | 2-3 hours | Deploy/fix contract or remove |
| #6 - Execution Connection | 2-3 hours | Wire event → executor pipeline |
| #10 - Validation | 1-2 hours | Add amount validation in parser |
| **Total** | **8-14 hours** | **Full system operational** |
**Expected Outcome:** System should achieve first profitable execution within 24 hours of fixing blockers #1-2.