package calculation_validation import ( "fmt" "math/big" ) // ProfitValidator validates arbitrage profit calculations type ProfitValidator struct { weiPerEth *big.Int tolerance float64 // Acceptable percentage difference } // NewProfitValidator creates a new profit validator func NewProfitValidator(tolerancePercent float64) *ProfitValidator { return &ProfitValidator{ weiPerEth: big.NewInt(1e18), tolerance: tolerancePercent, } } // ValidateOpportunity validates an opportunity's profit calculation func (pv *ProfitValidator) ValidateOpportunity(opp *OpportunityTestData) *ValidationResult { result := &ValidationResult{ OpportunityID: opp.ID, IsValid: true, Errors: []string{}, Warnings: []string{}, } // Validate threshold check if opp.ThresholdCheck != nil { thresholdValid := pv.validateThresholdCheck(opp.ThresholdCheck) if !thresholdValid { result.Errors = append(result.Errors, "threshold check failed") result.IsValid = false } } // Validate profit values if opp.NetProfitETH != nil { result.ExpectedProfit = opp.NetProfitETH // Check if profit is positive if opp.NetProfitETH.Sign() <= 0 && opp.IsExecutable { result.Errors = append(result.Errors, "executable opportunity has non-positive profit") result.IsValid = false } // Check if profit meets threshold if opp.ThresholdCheck != nil && opp.IsExecutable { if opp.NetProfitETH.Cmp(opp.ThresholdCheck.MinThreshold) < 0 { result.Errors = append(result.Errors, "executable opportunity below minimum threshold") result.IsValid = false } } } // Validate rejection reason consistency if opp.IsExecutable && opp.RejectReason != "" { result.Warnings = append(result.Warnings, "executable opportunity has reject reason") } if !opp.IsExecutable && opp.RejectReason == "" { result.Warnings = append(result.Warnings, "non-executable opportunity missing reject reason") } return result } // validateThresholdCheck validates a threshold check calculation func (pv *ProfitValidator) validateThresholdCheck(check *ThresholdCheck) bool { if check == nil { return false } // Verify the comparison is correct expected := check.NetProfit.Cmp(check.MinThreshold) >= 0 if expected != check.Passed { return false } return true } // ValidateV3Calculation validates a V3 swap calculation func (pv *ProfitValidator) ValidateV3Calculation(calc SwapCalculation) *ValidationResult { result := &ValidationResult{ OpportunityID: "v3_calculation", IsValid: true, Errors: []string{}, Warnings: []string{}, } // Check for zero outputs if calc.AmountOut.Sign() == 0 && calc.AmountIn.Sign() > 0 { result.Warnings = append(result.Warnings, "zero output with non-zero input") } // Check if finalOut matches amountOut (should be slightly less due to fees) if calc.FinalOut.Cmp(calc.AmountOut) > 0 { result.Errors = append(result.Errors, "finalOut greater than amountOut (impossible)") result.IsValid = false } // Calculate expected fee deduction expectedFee := pv.calculateV3Fee(calc.AmountOut, calc.Fee) expectedFinalOut := new(big.Int).Sub(calc.AmountOut, expectedFee) // Check if finalOut is within tolerance diff := new(big.Int).Sub(expectedFinalOut, calc.FinalOut) diff.Abs(diff) // Allow 1% difference for rounding tolerance := new(big.Int).Div(calc.FinalOut, big.NewInt(100)) if diff.Cmp(tolerance) > 0 && calc.FinalOut.Sign() > 0 { result.Warnings = append(result.Warnings, fmt.Sprintf("finalOut differs from expected: got %s, expected ~%s", calc.FinalOut.String(), expectedFinalOut.String())) } return result } // calculateV3Fee calculates the fee for a V3 swap func (pv *ProfitValidator) calculateV3Fee(amount *big.Int, feeTier uint32) *big.Int { // Fee tiers: 500 = 0.05%, 3000 = 0.3%, 10000 = 1% fee := new(big.Int).Mul(amount, big.NewInt(int64(feeTier))) fee.Div(fee, big.NewInt(1000000)) return fee } // ValidateBatch validates multiple opportunities and generates a report func (pv *ProfitValidator) ValidateBatch(opportunities []*OpportunityTestData) *TestReport { report := &TestReport{ ValidationResults: []*ValidationResult{}, } var totalError float64 errorCount := 0 for _, opp := range opportunities { result := pv.ValidateOpportunity(opp) report.ValidationResults = append(report.ValidationResults, result) if result.IsValid { report.ValidCalculations++ } else { report.InvalidCalculations++ } if result.PercentError > 0 { totalError += result.PercentError errorCount++ } // Track max error if result.Difference != nil { if report.MaxError == nil || result.Difference.Cmp(report.MaxError) > 0 { report.MaxError = new(big.Float).Set(result.Difference) } } } report.TotalOpportunities = len(opportunities) if errorCount > 0 { report.AveragePercentError = totalError / float64(errorCount) } return report } // CompareETHtoWei validates ETH to wei conversion func (pv *ProfitValidator) CompareETHtoWei(ethValue *big.Float, weiValue *big.Int) *ValidationResult { result := &ValidationResult{ OpportunityID: "eth_wei_conversion", IsValid: true, Errors: []string{}, } // Convert ETH to wei expectedWei := new(big.Float).Mul(ethValue, new(big.Float).SetInt(pv.weiPerEth)) expectedWeiInt, _ := expectedWei.Int(nil) // Compare if expectedWeiInt.Cmp(weiValue) != 0 { result.IsValid = false result.Errors = append(result.Errors, fmt.Sprintf("ETH to wei conversion mismatch: %s ETH should be %s wei, got %s wei", ethValue.String(), expectedWeiInt.String(), weiValue.String())) // Calculate difference diff := new(big.Int).Sub(expectedWeiInt, weiValue) result.Difference = new(big.Float).SetInt(diff) } return result } // ValidateThresholdComparison validates that a profit threshold comparison was done correctly // This is the CRITICAL validation for the bug we fixed func (pv *ProfitValidator) ValidateThresholdComparison( netProfitETH *big.Float, minProfitThresholdWei *big.Int, wasExecutable bool, ) *ValidationResult { result := &ValidationResult{ OpportunityID: "threshold_comparison", IsValid: true, Errors: []string{}, } // Convert threshold from wei to ETH for comparison minProfitETH := new(big.Float).Quo( new(big.Float).SetInt(minProfitThresholdWei), new(big.Float).SetInt(pv.weiPerEth), ) // Expected result: netProfitETH >= minProfitETH expectedExecutable := netProfitETH.Cmp(minProfitETH) >= 0 if expectedExecutable != wasExecutable { result.IsValid = false result.Errors = append(result.Errors, fmt.Sprintf("threshold comparison incorrect: %.6f ETH vs %.6f ETH threshold, should be executable=%v, got executable=%v", mustFloat64(netProfitETH), mustFloat64(minProfitETH), expectedExecutable, wasExecutable)) } result.ExpectedProfit = netProfitETH result.CalculatedProfit = minProfitETH return result } // mustFloat64 safely converts big.Float to float64 func mustFloat64(f *big.Float) float64 { val, _ := f.Float64() return val }