File size: 5,800 Bytes
086d1ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c01b9cd
 
 
 
 
 
 
a23bb47
c01b9cd
 
 
a23bb47
 
 
 
 
 
 
 
 
 
 
 
 
 
c01b9cd
 
a23bb47
 
c01b9cd
 
a23bb47
c01b9cd
 
086d1ff
 
 
c01b9cd
086d1ff
 
 
 
c01b9cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
086d1ff
 
c01b9cd
 
 
 
 
 
 
 
 
 
086d1ff
 
 
 
 
c01b9cd
086d1ff
c01b9cd
086d1ff
c01b9cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
086d1ff
 
 
 
c01b9cd
086d1ff
 
 
 
 
c01b9cd
086d1ff
 
 
 
 
 
 
c01b9cd
086d1ff
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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),
  };
}