import { FoundryResult } from "../tools/foundryRunner.js"; export type ErrorCategory = | "compiler_error" | "revert_no_message" | "revert_with_message" | "assertion_failed" | "timeout" | "unknown"; export interface LogAnalysis { category: ErrorCategory; summary: string; // 1-2 frases em linguagem natural para o LLM relevantLines: string[]; // máx 10 linhas do log original } /** * Extracts the most actionable compiler error lines from forge output. * Focuses on the actual error messages and file locations. */ function extractCompilerErrors(combined: string): string[] { const lines = combined.split("\n"); const errorLines: string[] = []; let inErrorBlock = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Start of an error block if (line.trim().startsWith("Error") || line.trim().startsWith("error[")) { inErrorBlock = true; } // Start of a warning block else if (line.trim().startsWith("Warning") || line.trim().startsWith("warning[")) { inErrorBlock = false; } // End of compilation output else if (line.includes("Compilation failed")) { inErrorBlock = false; } if (inErrorBlock && line.trim() !== "") { errorLines.push(line); } if (errorLines.length >= 40) break; } return errorLines; } export function analyzeFoundryLog(result: FoundryResult): LogAnalysis { if (result.timedOut) return { category: "timeout", summary: "Forge exceeded 60s. The exploit may have an infinite loop or blocking logic.", relevantLines: [], }; if (result.combined.includes("Compiler run failed")) { const errorLines = extractCompilerErrors(result.combined); // Detect specific compiler error patterns for targeted guidance let specificGuidance = ""; const fullOutput = result.combined; if (fullOutput.includes("File not found") || fullOutput.includes("Source") && fullOutput.includes("not found")) { specificGuidance = " Import path is WRONG — check remappings and use the pattern from existing tests."; } else if (fullOutput.includes("Identifier not found") || fullOutput.includes("not visible")) { specificGuidance = " Identifier/member not found — check function name, visibility, or declare a minimal interface."; } else if (fullOutput.includes("type conversion") || fullOutput.includes("Type") && fullOutput.includes("not implicitly convertible")) { specificGuidance = " Type mismatch — add explicit cast."; } else if (fullOutput.includes("Function") && fullOutput.includes("not found")) { specificGuidance = " Function signature is wrong — check the API reference and use the exact signature."; } return { category: "compiler_error", summary: `[COMPILER_ERROR] Solidity compilation failed.${specificGuidance} Check: wrong import paths, missing members, type mismatches. Use the project remappings and existing test import patterns.`, relevantLines: errorLines, }; } if (result.combined.includes("No tests found")) { return { category: "unknown", summary: "[COMPILER_ERROR] No tests found in ExploitTest. Ensure the contract is named exactly 'ExploitTest' and the test function is 'test_Exploit()'.", relevantLines: ["No tests found in ExploitTest"], }; } if (result.combined.includes("FAIL")) { const revertReason = result.combined.match(/revert: (.+)/)?.[1]; const customError = result.combined.match(/custom error '([^']+)'/)?.[1]; const assertionFail = result.combined.includes("Assertion Failed") || result.combined.includes("assertion failed"); const transferFail = result.combined.includes("TRANSFER_FROM_FAILED") || result.combined.includes("TRANSFER_FAILED"); if (assertionFail) { const assertLines = result.combined.split("\n") .filter(l => l.includes("assertion") || l.includes("FAIL") || l.includes("Left") || l.includes("Right")) .slice(0, 10); return { category: "assertion_failed", summary: "[ASSERTION_FAILED] The exploit ran but the final assertion failed — the attacker did not achieve the expected outcome. Re-check the exploit logic and expected values.", relevantLines: assertLines, }; } if (transferFail) return { category: "revert_with_message", summary: `[REVERT] Token transfer failed (TRANSFER_FROM_FAILED). The contract does not have enough tokens, or approval is missing. Setup token balances and approvals before the exploit.`, relevantLines: [result.combined.split("\n").find(l => l.includes("TRANSFER")) ?? "TRANSFER_FROM_FAILED"], }; if (customError) return { category: "revert_with_message", summary: `[REVERT] Contract reverted with custom error: "${customError}". Check what conditions trigger this error in the contract source.`, relevantLines: [customError], }; if (revertReason) return { category: "revert_with_message", summary: `[REVERT] Transaction reverted with: "${revertReason}". The contract rejected the operation — check permissions, roles, and call order.`, relevantLines: [revertReason], }; return { category: "revert_no_message", summary: "[REVERT_NO_MESSAGE] Transaction reverted without a message. Common causes: wrong call order, missing role/permission setup, incorrect contract state, or wrong function arguments.", relevantLines: result.combined.split("\n") .filter(l => l.includes("revert") || l.includes("FAIL")).slice(0, 5), }; } return { category: "unknown", summary: "[UNKNOWN_ERROR] Unexpected forge output. Review the full output below.", relevantLines: result.combined.split("\n").slice(0, 10), }; }