439 lines
10 KiB
Markdown
439 lines
10 KiB
Markdown
# Critical Fixes Implementation Plan
|
|
|
|
**Generated:** 2025-11-01
|
|
**Based On:** LOGIC_AUDIT_REPORT.md
|
|
**Priority:** IMMEDIATE - Production Blocking Issues
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
This document outlines the implementation plan for **6 critical issues** identified in the comprehensive logic audit. These issues prevent production deployment and must be fixed immediately.
|
|
|
|
**Current Status:** ⚠️ NOT PRODUCTION READY
|
|
**Target Status:** ✅ PRODUCTION READY
|
|
**Estimated Time:** 2-3 days for critical fixes
|
|
|
|
---
|
|
|
|
## Critical Issue #1: DFS Path Building Bug
|
|
|
|
### Location
|
|
`pkg/arbitrage/multihop.go:234-246`
|
|
|
|
### Problem
|
|
```go
|
|
// WRONG: Modifies underlying array
|
|
newPath := append(currentPath, pool)
|
|
newTokens := append(currentTokens, nextToken)
|
|
mhs.dfsArbitragePaths(...)
|
|
delete(visited, nextToken) // Backtrack - but slice is still modified!
|
|
```
|
|
|
|
### Impact
|
|
- Paths become contaminated with pools from other branches
|
|
- Invalid arbitrage paths that don't form profitable loops
|
|
- All multi-hop arbitrage is compromised
|
|
|
|
### Fix Implementation
|
|
```go
|
|
// CORRECT: Explicit copy
|
|
newPath := make([]Pool, len(currentPath)+1)
|
|
copy(newPath, currentPath)
|
|
newPath[len(currentPath)] = pool
|
|
|
|
newTokens := make([]string, len(currentTokens)+1)
|
|
copy(newTokens, currentTokens)
|
|
newTokens[len(currentTokens)] = nextToken
|
|
```
|
|
|
|
### Testing
|
|
```go
|
|
func TestDFSPathIsolation(t *testing.T) {
|
|
// Test that paths don't contaminate each other
|
|
// Create two branches from same root
|
|
// Verify each path contains only its own pools
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Critical Issue #2: Cache Poisoning
|
|
|
|
### Location
|
|
`pkg/arbitrage/multihop.go:708-720`
|
|
|
|
### Problem
|
|
```go
|
|
// WRONG: Only checks first path's timestamp
|
|
if len(paths) > 0 && time.Since(paths[0].LastUpdated) < mhs.cacheExpiry {
|
|
return paths, true // Returns potentially stale paths!
|
|
}
|
|
```
|
|
|
|
### Impact
|
|
- Trading on 30+ second old price data
|
|
- Guaranteed transaction failures and losses
|
|
- Flash loan repayment failures
|
|
|
|
### Fix Implementation
|
|
```go
|
|
// CORRECT: Check each path individually
|
|
validPaths := make([]Path, 0, len(paths))
|
|
for _, path := range paths {
|
|
if time.Since(path.LastUpdated) <= mhs.cacheExpiry {
|
|
validPaths = append(validPaths, path)
|
|
}
|
|
}
|
|
|
|
if len(validPaths) == 0 {
|
|
return nil, false // All paths stale, rebuild
|
|
}
|
|
|
|
return validPaths, true
|
|
```
|
|
|
|
### Testing
|
|
```go
|
|
func TestCacheExpiryPerPath(t *testing.T) {
|
|
// Create paths with different timestamps
|
|
// Verify only fresh paths are returned
|
|
// Verify stale paths trigger rebuild
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Critical Issue #3: Slippage Formula Mathematically Incorrect
|
|
|
|
### Location
|
|
`pkg/uniswap/pool_detector.go` and calculation helpers
|
|
|
|
### Problem
|
|
```go
|
|
// WRONG: Not how AMM slippage works
|
|
slippage = tradeSize / 2.0
|
|
```
|
|
|
|
### Impact
|
|
- Slippage estimates 2-5x incorrect
|
|
- Over-estimation rejects profitable trades
|
|
- Under-estimation causes failed transactions
|
|
|
|
### Fix Implementation
|
|
```go
|
|
// CORRECT: Proper constant product formula for Uniswap V2
|
|
func calculateSlippage(reserveIn, reserveOut, amountIn *big.Int) *big.Float {
|
|
// dy = (y * dx * 997) / (x * 1000 + dx * 997)
|
|
|
|
// Convert to big.Float for precision
|
|
x := new(big.Float).SetInt(reserveIn)
|
|
y := new(big.Float).SetInt(reserveOut)
|
|
dx := new(big.Float).SetInt(amountIn)
|
|
|
|
// Calculate: dx * 997
|
|
numerator1 := new(big.Float).Mul(dx, big.NewFloat(997))
|
|
|
|
// Calculate: y * dx * 997
|
|
numerator := new(big.Float).Mul(y, numerator1)
|
|
|
|
// Calculate: x * 1000
|
|
denom1 := new(big.Float).Mul(x, big.NewFloat(1000))
|
|
|
|
// Calculate: dx * 997
|
|
denom2 := new(big.Float).Mul(dx, big.NewFloat(997))
|
|
|
|
// Calculate: x * 1000 + dx * 997
|
|
denominator := new(big.Float).Add(denom1, denom2)
|
|
|
|
// Calculate: dy = numerator / denominator
|
|
dy := new(big.Float).Quo(numerator, denominator)
|
|
|
|
// Calculate market price: y / x
|
|
marketPrice := new(big.Float).Quo(y, x)
|
|
|
|
// Calculate execution price: dy / dx
|
|
executionPrice := new(big.Float).Quo(dy, dx)
|
|
|
|
// Slippage = (marketPrice - executionPrice) / marketPrice
|
|
priceDiff := new(big.Float).Sub(marketPrice, executionPrice)
|
|
slippage := new(big.Float).Quo(priceDiff, marketPrice)
|
|
|
|
return slippage
|
|
}
|
|
|
|
// For Uniswap V3, use different formula based on concentrated liquidity
|
|
func calculateSlippageV3(sqrtPriceX96, liquidity, amountIn *big.Int, tickSpacing int) *big.Float {
|
|
// V3-specific concentrated liquidity math
|
|
// Implementation based on Uniswap V3 Core whitepaper
|
|
}
|
|
```
|
|
|
|
### Testing
|
|
```go
|
|
func TestSlippageCalculation(t *testing.T) {
|
|
// Known reserve amounts and trade sizes
|
|
// Verify slippage matches expected AMM math
|
|
// Compare with on-chain actual execution
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Critical Issue #4: Gas Price Race Condition
|
|
|
|
### Location
|
|
`pkg/execution/executor.go:768`
|
|
|
|
### Problem
|
|
```go
|
|
// WRONG: Shared TransactOpts modified concurrently
|
|
transactOpts.GasPrice = biddingStrategy.PriorityFee // Line 768
|
|
```
|
|
|
|
### Impact
|
|
- Gas price from one opportunity bleeds into another
|
|
- Underpayment or overpayment for transactions
|
|
- Financial loss and failed transactions
|
|
|
|
### Fix Implementation
|
|
```go
|
|
// CORRECT: Deep copy TransactOpts per execution
|
|
func (e *Executor) executeArbitrage(opp Opportunity) error {
|
|
// Create new TransactOpts for this execution
|
|
txOpts := &bind.TransactOpts{
|
|
From: e.baseOpts.From,
|
|
Signer: e.baseOpts.Signer,
|
|
Value: big.NewInt(0),
|
|
GasLimit: 0, // Will be estimated
|
|
Context: context.Background(),
|
|
}
|
|
|
|
// Calculate gas price for THIS opportunity
|
|
gasPrice := e.calculateGasPrice(opp)
|
|
txOpts.GasPrice = gasPrice
|
|
|
|
// Use this isolated txOpts
|
|
tx, err := e.contract.ExecuteArbitrage(txOpts, ...)
|
|
|
|
return err
|
|
}
|
|
```
|
|
|
|
### Testing
|
|
```go
|
|
func TestConcurrentGasPriceIsolation(t *testing.T) {
|
|
// Execute multiple opportunities concurrently
|
|
// Verify each uses correct gas price
|
|
// Check for no interference between executions
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Critical Issue #5: Float-to-Int Conversion Loses Precision
|
|
|
|
### Location
|
|
`pkg/arbitrage/calculator.go` (profit calculations)
|
|
|
|
### Problem
|
|
```go
|
|
// WRONG: Truncates all decimals
|
|
profitFloat := calculateProfit() // Returns big.Float
|
|
profitInt, _ := profitFloat.Int(nil) // Truncates!
|
|
```
|
|
|
|
### Impact
|
|
- Valid profits <1 wei are rejected
|
|
- Small arbitrage (0.0005 ETH) shows as 0 profit
|
|
- Missing profitable opportunities
|
|
|
|
### Fix Implementation
|
|
```go
|
|
// CORRECT: Multiply by 1e18 before converting
|
|
func convertProfitToWei(profitETH *big.Float) *big.Int {
|
|
// Multiply by 10^18 to preserve decimal places
|
|
weiMultiplier := new(big.Float).SetInt(new(big.Int).Exp(
|
|
big.NewInt(10),
|
|
big.NewInt(18),
|
|
nil,
|
|
))
|
|
|
|
profitWei := new(big.Float).Mul(profitETH, weiMultiplier)
|
|
|
|
// Convert to int (now represents wei)
|
|
result := new(big.Int)
|
|
profitWei.Int(result)
|
|
|
|
return result
|
|
}
|
|
|
|
// Usage
|
|
profitFloat := calculateProfit() // Returns profit in ETH as big.Float
|
|
profitWei := convertProfitToWei(profitFloat) // Convert to wei
|
|
|
|
if profitWei.Cmp(minProfitWei) >= 0 {
|
|
// Execute arbitrage
|
|
}
|
|
```
|
|
|
|
### Testing
|
|
```go
|
|
func TestProfitPrecision(t *testing.T) {
|
|
// Test 0.0001 ETH profit converts correctly
|
|
// Test 0.0000001 ETH profit converts correctly
|
|
// Verify no truncation occurs
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Critical Issue #6: Opportunity Handler Race Condition
|
|
|
|
### Location
|
|
`pkg/arbitrage/detection_engine.go:749-752`
|
|
|
|
### Problem
|
|
```go
|
|
// WRONG: Unbounded concurrency
|
|
go h.handler(opp) // Creates thousands of goroutines!
|
|
```
|
|
|
|
### Impact
|
|
- High opportunity rate creates OOM conditions
|
|
- System instability and crashes
|
|
- Resource exhaustion
|
|
|
|
### Fix Implementation
|
|
```go
|
|
// CORRECT: Semaphore for backpressure
|
|
type HandlerPool struct {
|
|
sem chan struct{}
|
|
maxWorkers int
|
|
}
|
|
|
|
func NewHandlerPool(maxWorkers int) *HandlerPool {
|
|
return &HandlerPool{
|
|
sem: make(chan struct{}, maxWorkers),
|
|
maxWorkers: maxWorkers,
|
|
}
|
|
}
|
|
|
|
func (p *HandlerPool) Handle(handler func(Opportunity), opp Opportunity) {
|
|
// Acquire semaphore (blocks if at max capacity)
|
|
p.sem <- struct{}{}
|
|
|
|
go func() {
|
|
defer func() {
|
|
<-p.sem // Release semaphore
|
|
}()
|
|
|
|
handler(opp)
|
|
}()
|
|
}
|
|
|
|
// Usage in detection engine
|
|
handlerPool := NewHandlerPool(10) // Max 10 concurrent handlers
|
|
|
|
for _, opp := range opportunities {
|
|
handlerPool.Handle(h.handler, opp)
|
|
}
|
|
```
|
|
|
|
### Testing
|
|
```go
|
|
func TestHandlerBackpressure(t *testing.T) {
|
|
// Generate 1000 opportunities
|
|
// Verify max 10 concurrent handlers
|
|
// Check no OOM condition
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Order
|
|
|
|
### Day 1: Critical Math & Concurrency
|
|
1. **Morning:** Fix #3 (Slippage Formula) - Most complex
|
|
2. **Afternoon:** Fix #5 (Float-to-Int Precision)
|
|
3. **EOD:** Testing for both
|
|
|
|
### Day 2: Path & Cache Issues
|
|
1. **Morning:** Fix #1 (DFS Path Building)
|
|
2. **Afternoon:** Fix #2 (Cache Poisoning)
|
|
3. **EOD:** Integration testing
|
|
|
|
### Day 3: Execution & Stabilization
|
|
1. **Morning:** Fix #4 (Gas Price Race)
|
|
2. **Morning:** Fix #6 (Handler Backpressure)
|
|
3. **Afternoon:** Full system testing
|
|
4. **EOD:** Performance validation
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Tests
|
|
- Each fix gets dedicated test
|
|
- Edge cases covered
|
|
- Regression prevention
|
|
|
|
### Integration Tests
|
|
```bash
|
|
# Run full test suite
|
|
go test ./pkg/arbitrage/... -v -race
|
|
go test ./pkg/execution/... -v -race
|
|
go test ./pkg/uniswap/... -v -race
|
|
```
|
|
|
|
### Load Testing
|
|
```bash
|
|
# Simulate high opportunity rate
|
|
# Verify no resource exhaustion
|
|
# Check profit calculations under load
|
|
```
|
|
|
|
### Fork Testing
|
|
```bash
|
|
# Test against Arbitrum mainnet fork
|
|
# Verify actual arbitrage execution
|
|
# Validate profit calculations match on-chain
|
|
```
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
- [ ] All 6 critical issues fixed
|
|
- [ ] All new tests passing
|
|
- [ ] No race conditions detected (`go test -race`)
|
|
- [ ] Fork testing shows profitable arbitrage
|
|
- [ ] System handles 1000+ opportunities/sec
|
|
- [ ] Memory usage stable under load
|
|
- [ ] Profit calculations match on-chain execution
|
|
|
|
---
|
|
|
|
## Rollout Plan
|
|
|
|
1. **Dev Environment:** Apply and test all fixes
|
|
2. **Fork Testing:** Validate against mainnet fork
|
|
3. **Testnet Deployment:** Deploy to Arbitrum testnet
|
|
4. **Monitoring:** 24h observation period
|
|
5. **Production:** Gradual rollout with kill switch ready
|
|
|
|
---
|
|
|
|
## Documentation Updates
|
|
|
|
After fixes applied:
|
|
- [ ] Update `LOGIC_AUDIT_REPORT.md` with fix status
|
|
- [ ] Create `FIXES_APPLIED.md` with before/after examples
|
|
- [ ] Update `PROJECT_SPECIFICATION.md` with corrected algorithms
|
|
- [ ] Add fix details to `CHANGELOG.md`
|
|
|
|
---
|
|
|
|
**Status:** 📋 Planning Complete - Ready for Implementation
|
|
**Next Action:** Begin Day 1 implementation (Slippage Formula fix)
|