Tales-Cunha commited on
Commit
7e26449
·
1 Parent(s): 7e6051f

feat: update the archtecture from the testet agent

Browse files

I add new prompts and new structure. I also add the benchmark to
evaluate the agent through time and change. I am using the same model

data/benchmark_summary.json ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "summary": {
3
+ "timestamp": "2026-06-07T21:36:40.793Z",
4
+ "total_processed": 22,
5
+ "reproducibility_rate": 27.27272727272727,
6
+ "specificity_rate": 0,
7
+ "overall_ground_truth_rate": 0,
8
+ "average_iterations": 8.181818181818182
9
+ },
10
+ "details": [
11
+ {
12
+ "id": "001",
13
+ "reproducible": false,
14
+ "specific": false,
15
+ "iterations": 10
16
+ },
17
+ {
18
+ "id": "003",
19
+ "reproducible": true,
20
+ "specific": false,
21
+ "iterations": 5
22
+ },
23
+ {
24
+ "id": "008",
25
+ "reproducible": true,
26
+ "specific": false,
27
+ "iterations": 5
28
+ },
29
+ {
30
+ "id": "009",
31
+ "reproducible": false,
32
+ "specific": false,
33
+ "iterations": 10
34
+ },
35
+ {
36
+ "id": "015",
37
+ "reproducible": false,
38
+ "specific": false,
39
+ "iterations": 10
40
+ },
41
+ {
42
+ "id": "018",
43
+ "reproducible": false,
44
+ "specific": false,
45
+ "iterations": 10
46
+ },
47
+ {
48
+ "id": "020",
49
+ "reproducible": false,
50
+ "specific": false,
51
+ "iterations": 10
52
+ },
53
+ {
54
+ "id": "032",
55
+ "reproducible": false,
56
+ "specific": false,
57
+ "iterations": 10
58
+ },
59
+ {
60
+ "id": "033",
61
+ "reproducible": false,
62
+ "specific": false,
63
+ "iterations": 10
64
+ },
65
+ {
66
+ "id": "039",
67
+ "reproducible": false,
68
+ "specific": false,
69
+ "iterations": 10
70
+ },
71
+ {
72
+ "id": "041",
73
+ "reproducible": false,
74
+ "specific": false,
75
+ "iterations": 10
76
+ },
77
+ {
78
+ "id": "042",
79
+ "reproducible": false,
80
+ "specific": false,
81
+ "iterations": 10
82
+ },
83
+ {
84
+ "id": "048",
85
+ "reproducible": false,
86
+ "specific": false,
87
+ "iterations": 10
88
+ },
89
+ {
90
+ "id": "049",
91
+ "reproducible": false,
92
+ "specific": false,
93
+ "iterations": 10
94
+ },
95
+ {
96
+ "id": "051",
97
+ "reproducible": true,
98
+ "specific": false,
99
+ "iterations": 1
100
+ },
101
+ {
102
+ "id": "054",
103
+ "reproducible": true,
104
+ "specific": false,
105
+ "iterations": 4
106
+ },
107
+ {
108
+ "id": "058",
109
+ "reproducible": false,
110
+ "specific": false,
111
+ "iterations": 10
112
+ },
113
+ {
114
+ "id": "066",
115
+ "reproducible": false,
116
+ "specific": false,
117
+ "iterations": 10
118
+ },
119
+ {
120
+ "id": "070",
121
+ "reproducible": false,
122
+ "specific": false,
123
+ "iterations": 10
124
+ },
125
+ {
126
+ "id": "077",
127
+ "reproducible": false,
128
+ "specific": false,
129
+ "iterations": 10
130
+ },
131
+ {
132
+ "id": "091",
133
+ "reproducible": true,
134
+ "specific": false,
135
+ "iterations": 4
136
+ },
137
+ {
138
+ "id": "098",
139
+ "reproducible": true,
140
+ "specific": false,
141
+ "iterations": 1
142
+ }
143
+ ]
144
+ }
scripts/setup-sandbox.sh CHANGED
@@ -1,7 +1,7 @@
1
  #!/bin/bash
2
  set -e
3
 
4
- SANDBOX="/tmp/poc-sandbox"
5
 
6
  # Tenta encontrar forge no PATH se a variável não estiver definida ou falhar
7
  if [ -z "$FORGE_BIN" ] || [ ! -f "$FORGE_BIN" ]; then
 
1
  #!/bin/bash
2
  set -e
3
 
4
+ SANDBOX="${SANDBOX_DIR:-/tmp/poc-sandbox}"
5
 
6
  # Tenta encontrar forge no PATH se a variável não estiver definida ou falhar
7
  if [ -z "$FORGE_BIN" ] || [ ! -f "$FORGE_BIN" ]; then
src/agents/tester/agent.ts CHANGED
@@ -4,12 +4,20 @@ import { PoCStateAnnotation, PoCState } from "./state.js";
4
  import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
5
  import { OracleContext } from "./types.js";
6
  import { createLLM } from "../../config/llm.ts";
7
- import { SYSTEM_PROMPT } from "./prompts/system.js";
 
 
 
 
 
 
8
  import { extractSolidity } from "./utils/extractSolidity.js";
9
  import { runFoundry } from "./tools/foundryRunner.js";
10
  import { analyzeFoundryLog } from "./utils/logAnalyzer.js";
 
 
11
 
12
- const MAX_ITERATIONS = 5;
13
 
14
  const llm = createLLM();
15
 
@@ -17,47 +25,128 @@ async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
17
  console.log("[oracleNode] gerando scaffold para:", state.report.title);
18
 
19
  const solidityScaffold = generateLocalScaffold(state.report);
20
- const oracleContext: OracleContext = { solidityScaffold };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- console.log("[oracleNode] scaffold gerado, tamanho:", solidityScaffold.length, "chars");
23
  return { oracleContext };
24
  }
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
27
- const { report, oracleContext, executionLogs, pocCode, iterations, lastError } = state;
28
  const isRetry = iterations > 0;
29
 
30
- const userMessage = isRetry
31
- ? `O seguinte exploit FALHOU no Foundry.
32
 
33
- Código anterior:
 
 
 
 
 
 
34
  \`\`\`solidity
35
- ${pocCode}
36
  \`\`\`
37
 
38
- Output do Forge (última execução):
39
- ${executionLogs[executionLogs.length - 1]?.slice(0, 3000) ?? "sem logs"}
40
-
41
- Análise do erro: ${lastError ?? "desconhecido"}
42
 
43
- Corrija o código. Retorne o arquivo Solidity completo corrigido.`
44
- : `Relatório de Vulnerabilidade:
45
- - Título: ${report.title}
46
- - Tipo: ${report.type}
47
- - Descrição: ${report.description}
48
- - Vetor de Ataque: ${report.attackVector}
49
- ${report.exploitablePaths ? `- Caminhos de Exploração:\n * ${report.exploitablePaths.join("\n * ")}` : ""}
50
 
51
- Scaffold (complete APENAS test_Exploit):
52
  \`\`\`solidity
53
  ${oracleContext!.solidityScaffold}
54
- \`\`\``;
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- console.log(`[testerAgent] generatePoCNode iteração ${iterations + 1}, isRetry=${isRetry}`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
  try {
59
  const response = await llm.invoke([
60
- { role: "system", content: SYSTEM_PROMPT },
61
  { role: "user", content: userMessage },
62
  ]);
63
  const solidityCode = extractSolidity(response.content as string);
@@ -77,14 +166,22 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
77
  const isMissingContract = !trimmedCode.includes("contract ExploitTest");
78
  const isMissingTest = !trimmedCode.includes("function test_Exploit()");
79
  const isPlaceholder = trimmedCode.includes("TODO: implementar exploit");
80
- if (isMissingCode || isMissingContract || isMissingTest || isPlaceholder) {
 
 
 
 
 
 
81
  const summary = state.lastError ?? (isMissingCode
82
  ? "Código Solidity ausente. O LLM não retornou o arquivo do exploit."
83
  : isMissingContract
84
  ? "Contrato ExploitTest não encontrado no arquivo."
85
  : isMissingTest
86
  ? "Função test_Exploit() não encontrada no arquivo."
87
- : "Exploit não implementado (placeholder TODO ainda presente)."
 
 
88
  );
89
  const status = state.iterations >= MAX_ITERATIONS ? "failed" : "running";
90
  return {
@@ -94,7 +191,7 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
94
  };
95
  }
96
 
97
- const result = await runFoundry(state.pocCode);
98
  const analysis = analyzeFoundryLog(result);
99
  const noTestsFound = result.combined.includes("No tests found");
100
  const summary = noTestsFound
@@ -118,30 +215,16 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
118
 
119
  return {
120
  executionLogs: [result.combined], // reducer append
121
- lastError: summary,
122
  status,
123
  };
124
  }
125
 
126
  async function reflectNode(state: PoCState): Promise<Partial<PoCState>> {
127
- const lastLog = state.executionLogs[state.executionLogs.length - 1];
128
- if (!lastLog) {
129
- return { lastError: "Sem logs disponíveis para análise." };
130
- }
131
-
132
- const mockResult = {
133
- exitCode: 1, timedOut: lastLog.includes("TIMEOUT"),
134
- stdout: "", stderr: "", combined: lastLog,
135
- };
136
-
137
- const analysis = analyzeFoundryLog(mockResult as any);
138
-
139
- console.log(`[reflectNode] categoria: ${analysis.category}`);
140
- console.log(`[reflectNode] resumo: ${analysis.summary}`);
141
-
142
- return {
143
- lastError: `[${analysis.category.toUpperCase()}] ${analysis.summary}\n\nLinhas relevantes:\n${analysis.relevantLines.join("\n")}`,
144
- };
145
  }
146
 
147
  function routeAfterFoundry(state: PoCState): "reflectNode" | typeof END {
@@ -153,11 +236,13 @@ function routeAfterFoundry(state: PoCState): "reflectNode" | typeof END {
153
 
154
  const graph = new StateGraph(PoCStateAnnotation)
155
  .addNode("oracleNode", oracleNode)
 
156
  .addNode("generatePoCNode", generatePoCNode)
157
  .addNode("runFoundryNode", runFoundryNode)
158
  .addNode("reflectNode", reflectNode)
159
  .addEdge(START, "oracleNode")
160
- .addEdge("oracleNode", "generatePoCNode")
 
161
  .addEdge("generatePoCNode", "runFoundryNode")
162
  .addConditionalEdges("runFoundryNode", routeAfterFoundry, {
163
  reflectNode: "reflectNode",
 
4
  import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
5
  import { OracleContext } from "./types.js";
6
  import { createLLM } from "../../config/llm.ts";
7
+ import {
8
+ SYSTEM_PROMPT,
9
+ ANALYZE_VULNERABILITY_PROMPT,
10
+ POC_INITIAL_PROMPT,
11
+ POC_COMPILE_FIX_PROMPT,
12
+ POC_TEST_FIX_PROMPT
13
+ } from "./prompts/system.js";
14
  import { extractSolidity } from "./utils/extractSolidity.js";
15
  import { runFoundry } from "./tools/foundryRunner.js";
16
  import { analyzeFoundryLog } from "./utils/logAnalyzer.js";
17
+ import { extractConstructor } from "./utils/parserUtils.js";
18
+ import { analyzeSolidityFile } from "../auditor/tools/solidity-analyzer-tool.js";
19
 
20
+ const MAX_ITERATIONS = 10;
21
 
22
  const llm = createLLM();
23
 
 
25
  console.log("[oracleNode] gerando scaffold para:", state.report.title);
26
 
27
  const solidityScaffold = generateLocalScaffold(state.report);
28
+
29
+ // Extrair info do constructor para ajudar o LLM no setUp
30
+ const constructorInfo = extractConstructor(state.report.affectedContract.sourceCode, state.report.affectedContract.name);
31
+
32
+ // STEP 3: Automated API Discovery
33
+ console.log("[oracleNode] analisando API do contrato e helpers de teste...");
34
+ const targetContractAPI = await analyzeSolidityFile(state.report.affectedContract.sourceCode, "short");
35
+
36
+ let referenceTestHelpers = "";
37
+ if (state.report.referenceTestCode) {
38
+ referenceTestHelpers = await analyzeSolidityFile(state.report.referenceTestCode, "short");
39
+ }
40
+
41
+ const oracleContext: OracleContext = {
42
+ solidityScaffold,
43
+ constructorInfo: constructorInfo?.parameters,
44
+ targetContractAPI,
45
+ referenceTestHelpers
46
+ };
47
 
48
+ console.log("[oracleNode] scaffold gerado, context built.");
49
  return { oracleContext };
50
  }
51
 
52
+ /**
53
+ * NEW: Multi-Pass Node 1 - Analysis
54
+ */
55
+ async function analyzeVulnerabilityNode(state: PoCState): Promise<Partial<PoCState>> {
56
+ console.log("[testerAgent] analyzeVulnerabilityNode: analyzing bug...");
57
+
58
+ const userMessage = `Vulnerability Report:
59
+ - Title: ${state.report.title}
60
+ - Type: ${state.report.type}
61
+ - Description: ${state.report.description}
62
+
63
+ ### Target Contract Source Code (${state.report.affectedContract.name}):
64
+ \`\`\`solidity
65
+ ${state.report.affectedContract.sourceCode}
66
+ \`\`\`
67
+
68
+ ### Target Contract API:
69
+ ${state.oracleContext!.targetContractAPI}
70
+
71
+ ${state.oracleContext!.referenceTestHelpers ? `### Environment Helpers:
72
+ ${state.oracleContext!.referenceTestHelpers}` : ""}
73
+ `;
74
+
75
+ const response = await llm.invoke([
76
+ { role: "system", content: ANALYZE_VULNERABILITY_PROMPT },
77
+ { role: "user", content: userMessage },
78
+ ]);
79
+
80
+ return { vulnerabilityAnalysis: response.content as string };
81
+ }
82
+
83
+ /**
84
+ * Multi-Pass Node 2 - Code Generation (Initial & Fixes)
85
+ */
86
  async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
87
+ const { report, oracleContext, executionLogs, pocCode, iterations, lastError, vulnerabilityAnalysis } = state;
88
  const isRetry = iterations > 0;
89
 
90
+ let currentSystemPrompt = SYSTEM_PROMPT;
91
+ let userMessage = "";
92
 
93
+ if (!isRetry) {
94
+ // PASS 2: INITIAL GENERATION
95
+ currentSystemPrompt = POC_INITIAL_PROMPT;
96
+ userMessage = `Vulnerability Analysis Plan:
97
+ ${vulnerabilityAnalysis}
98
+
99
+ ### Contract Source:
100
  \`\`\`solidity
101
+ ${report.affectedContract.sourceCode}
102
  \`\`\`
103
 
104
+ ### API Reference:
105
+ ${oracleContext!.targetContractAPI}
 
 
106
 
107
+ ${oracleContext!.referenceTestHelpers ? `### Test Helpers:
108
+ ${oracleContext!.referenceTestHelpers}` : ""}
 
 
 
 
 
109
 
110
+ ### Scaffold:
111
  \`\`\`solidity
112
  ${oracleContext!.solidityScaffold}
113
+ \`\`\`
114
+ `;
115
+ } else {
116
+ // PASS 3+: FIXING ERRORS (BRANCHING)
117
+ const isCompilerError = lastError?.includes("[COMPILER_ERROR]");
118
+ currentSystemPrompt = isCompilerError ? POC_COMPILE_FIX_PROMPT : POC_TEST_FIX_PROMPT;
119
+
120
+ userMessage = `The previous PoC failed.
121
+
122
+ Error Category: ${isCompilerError ? "Compilation Failure" : "Execution/Logic Failure"}
123
+ Forge Output:
124
+ ${executionLogs[executionLogs.length - 1]?.slice(0, 3000) ?? "sem logs"}
125
 
126
+ Previous Code:
127
+ \`\`\`solidity
128
+ ${pocCode}
129
+ \`\`\`
130
+
131
+ Analysis of the bug:
132
+ ${vulnerabilityAnalysis}
133
+
134
+ Fix the code. Return the entire file.`;
135
+ }
136
+
137
+ console.log(`[testerAgent] generatePoCNode iteração ${iterations + 1}, isRetry=${isRetry}, mode=${isRetry ? (lastError?.includes("[COMPILER_ERROR]") ? "FIX_COMPILE" : "FIX_LOGIC") : "INITIAL"}`);
138
+
139
+ // DEBUG: Output context before sending to LLM
140
+ if (process.env.DEBUG_CONTEXT === "true") {
141
+ console.log("\n" + "=".repeat(20) + " LLM CONTEXT START " + "=".repeat(20));
142
+ console.log("System Prompt:", currentSystemPrompt);
143
+ console.log("User Message:", userMessage);
144
+ console.log("=".repeat(20) + " LLM CONTEXT END " + "=".repeat(20) + "\n");
145
+ }
146
 
147
  try {
148
  const response = await llm.invoke([
149
+ { role: "system", content: currentSystemPrompt },
150
  { role: "user", content: userMessage },
151
  ]);
152
  const solidityCode = extractSolidity(response.content as string);
 
166
  const isMissingContract = !trimmedCode.includes("contract ExploitTest");
167
  const isMissingTest = !trimmedCode.includes("function test_Exploit()");
168
  const isPlaceholder = trimmedCode.includes("TODO: implementar exploit");
169
+ const isLazyTest = (
170
+ trimmedCode.includes("assertTrue(true") ||
171
+ trimmedCode.includes("assert(true") ||
172
+ trimmedCode.includes("assert(1 == 1")
173
+ ) && !trimmedCode.includes("assertEq") && !trimmedCode.includes("assertGt") && !trimmedCode.includes("assertLe") && !trimmedCode.includes("assertGe") && !trimmedCode.includes("assertNotEq");
174
+
175
+ if (isMissingCode || isMissingContract || isMissingTest || isPlaceholder || isLazyTest) {
176
  const summary = state.lastError ?? (isMissingCode
177
  ? "Código Solidity ausente. O LLM não retornou o arquivo do exploit."
178
  : isMissingContract
179
  ? "Contrato ExploitTest não encontrado no arquivo."
180
  : isMissingTest
181
  ? "Função test_Exploit() não encontrada no arquivo."
182
+ : isPlaceholder
183
+ ? "Exploit não implementado (placeholder TODO ainda presente)."
184
+ : "Exploit muito fraco (assertTrue(true)). Você deve provar a vulnerabilidade com uma asserção real (ex: assertEq, assertGt)."
185
  );
186
  const status = state.iterations >= MAX_ITERATIONS ? "failed" : "running";
187
  return {
 
191
  };
192
  }
193
 
194
+ const result = await runFoundry(state.pocCode, state.report.customSandboxDir);
195
  const analysis = analyzeFoundryLog(result);
196
  const noTestsFound = result.combined.includes("No tests found");
197
  const summary = noTestsFound
 
215
 
216
  return {
217
  executionLogs: [result.combined], // reducer append
218
+ lastError: passed ? null : `[${analysis.category.toUpperCase()}] ${summary}`,
219
  status,
220
  };
221
  }
222
 
223
  async function reflectNode(state: PoCState): Promise<Partial<PoCState>> {
224
+ // Now reflectNode is simpler as we moved the logic to specialized prompts in generatePoCNode
225
+ // But we still use it to log the reflection
226
+ console.log(`[reflectNode] reflecting on error: ${state.lastError}`);
227
+ return {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  }
229
 
230
  function routeAfterFoundry(state: PoCState): "reflectNode" | typeof END {
 
236
 
237
  const graph = new StateGraph(PoCStateAnnotation)
238
  .addNode("oracleNode", oracleNode)
239
+ .addNode("analyzeVulnerabilityNode", analyzeVulnerabilityNode)
240
  .addNode("generatePoCNode", generatePoCNode)
241
  .addNode("runFoundryNode", runFoundryNode)
242
  .addNode("reflectNode", reflectNode)
243
  .addEdge(START, "oracleNode")
244
+ .addEdge("oracleNode", "analyzeVulnerabilityNode")
245
+ .addEdge("analyzeVulnerabilityNode", "generatePoCNode")
246
  .addEdge("generatePoCNode", "runFoundryNode")
247
  .addConditionalEdges("runFoundryNode", routeAfterFoundry, {
248
  reflectNode: "reflectNode",
src/agents/tester/index.ts CHANGED
@@ -9,7 +9,7 @@ import { VulnerabilityReport, PoCResult } from "./types.js";
9
  export async function runPoCGenerator(report: VulnerabilityReport): Promise<PoCResult> {
10
  console.log(`[runPoCGenerator] Iniciando para: ${report.id} — ${report.title}`);
11
 
12
- const finalState = await testerAgent.invoke({ report });
13
 
14
  const result: PoCResult = {
15
  reportId: report.id,
 
9
  export async function runPoCGenerator(report: VulnerabilityReport): Promise<PoCResult> {
10
  console.log(`[runPoCGenerator] Iniciando para: ${report.id} — ${report.title}`);
11
 
12
+ const finalState = await testerAgent.invoke({ report }, { recursionLimit: 100 });
13
 
14
  const result: PoCResult = {
15
  reportId: report.id,
src/agents/tester/prompts/system.ts CHANGED
@@ -1,96 +1,54 @@
1
  export const SYSTEM_PROMPT = `You are an expert smart contract security testing specialist. Your mission is to generate executable Proof-of-Concept (PoC) exploits demonstrating vulnerabilities using Foundry.
2
 
3
- ## PoC Explainability
4
- Write exploits as executable demonstrations that clearly prove the vulnerability. Include detailed comments documenting each attack step, the vulnerability being exploited, and why the exploit succeeds. The PoC must be self-explanatory to security auditors.
 
 
 
 
 
5
 
6
- ## Vulnerability Analysis
7
- Parse the vulnerability description provided and analyze the vulnerability type, affected code sections, and potential impact. Analyze the contract logic to understand the root cause before developing exploits.
8
 
9
- ## Testing Framework Guidelines
10
- Use Foundry exclusively for testing. Utilize Foundry cheatcodes for test control: "vm.prank()" for identity switching, "vm.deal()" for ETH funding, "vm.warp()" for time manipulation, "vm.expectRevert()" for failure testing.
11
 
12
- ## Scaffold Strict Compliance
13
- - The target contract's full source code is ALREADY included at the top of the scaffold. You can and MUST call its functions directly (e.g., \`target.deposit()\`). Do NOT create fake interfaces or use low-level \`.call(abi.encodeWithSignature(...))\`.
14
- - YOU MUST RETURN THE ENTIRE FILE PROVIDED IN THE SCAFFOLD. Do not omit the \`setUp()\` function or the original contract source code. Your output will overwrite the file directly.
15
- - DO NOT rename \`test_Exploit()\`. You MUST implement your exploit inside \`function test_Exploit() public\`.
16
- - DO NOT use characters with accents (like ã, ç, é, etc.) in string literals (e.g., inside \`assertEq\` or \`require\`). Use ONLY plain ASCII, or prefix with \`unicode"..."\` to avoid Solc compiler errors.
17
 
18
- ## PoC Executability
19
- Ensure all generated code compiles successfully. Write ONLY the test file code (helper contracts + ExploitTest). Resolve all compilation errors and logic reverts while preserving original contract logic.
20
 
21
- ## Iterative Refinement
22
- Debug compilation errors and test failures systematically using Forge output. If stuck on the same issue for >3 attempts, shift to a minimal working demonstration—proving the vulnerability exists matters more than setup complexity.
23
 
24
- ## Exploit Soundness
25
- The assertion in your test MUST prove the vulnerability. For example, if funds are stolen, assert that the vault balance decreased and the attacker balance increased.
26
 
27
- ## Output Format
28
- Return ONLY a code block with the full ExploitTest contract and any helper attacker contracts. Do not include markdown outside the code block.
 
 
 
29
 
30
- ## Examples (Few-Shot)
31
 
32
- **Input Example:**
33
- Vulnerability: Reentrancy in withdraw() allows draining the contract.
34
- Scaffold:
35
- \`\`\`solidity
36
- // SPDX-License-Identifier: UNLICENSED
37
- pragma solidity ^0.8.20;
38
- import "forge-std/Test.sol";
39
- contract Target { function withdraw(uint256) public {} } // Source code
40
- contract ExploitTest is Test {
41
- Target target;
42
- function setUp() public { target = new Target(); }
43
- function test_Exploit() public {
44
- // TODO: implementar exploit aqui
45
- }
46
- }
47
- \`\`\`
48
 
49
- **Expected Output:**
50
- \`\`\`solidity
51
- // SPDX-License-Identifier: UNLICENSED
52
- pragma solidity ^0.8.20;
53
- import "forge-std/Test.sol";
54
- contract Target { function withdraw(uint256) public {} } // Source code
55
 
56
- // We can define helper contracts outside the main test contract
57
- contract Attacker {
58
- Target target;
59
- constructor(address _target) {
60
- target = Target(_target);
61
- }
62
- fallback() external payable {
63
- if (address(target).balance >= 1 ether) {
64
- target.withdraw(1 ether);
65
- }
66
- }
67
- function attack() external {
68
- target.withdraw(1 ether);
69
- }
70
- }
71
 
72
- contract ExploitTest is Test {
73
- Target target;
74
-
75
- // IMPORTANT: We include the EXACT setUp() provided in the scaffold.
76
- function setUp() public {
77
- target = new Target();
78
- }
79
 
80
- function test_Exploit() public {
81
- vm.startPrank(address(0xBEEF));
82
-
83
- // 1. Deploy malicious contract
84
- Attacker attacker = new Attacker(address(target));
85
-
86
- // 2. Exploit the vulnerability using direct function calls
87
- attacker.attack();
88
-
89
- // 3. Verify the exploit succeeded (no special characters in assertion strings)
90
- assertEq(address(target).balance, 0, "Target contract should be drained");
91
-
92
- vm.stopPrank();
93
- }
94
- }
95
- \`\`\`
96
  `.trim();
 
1
  export const SYSTEM_PROMPT = `You are an expert smart contract security testing specialist. Your mission is to generate executable Proof-of-Concept (PoC) exploits demonstrating vulnerabilities using Foundry.
2
 
3
+ ## General Guidelines
4
+ - Use Foundry exclusively.
5
+ - Use \`vm.prank()\`, \`vm.deal()\`, \`vm.warp()\`, \`vm.expectRevert()\` as needed.
6
+ - **NO PLACEHOLDER TESTS:** Never write a test that only contains \`assertTrue(true)\`. You MUST use concrete assertions to prove the exploit's impact.
7
+ - **Context Compliance:** Reuse existing imports and setup patterns found in the provided code/reference tests.
8
+ - DO NOT rename \`test_Exploit()\`.
9
+ `.trim();
10
 
11
+ export const ANALYZE_VULNERABILITY_PROMPT = `Analyze the vulnerability in the following Solidity contract and provide a clear understanding of the issue.
 
12
 
13
+ Focus on understanding the root cause and mechanism of the vulnerability.
 
14
 
15
+ Please provide:
16
+ 1. **Clear explanation**: What is the bug?
17
+ 2. **Exploit path**: Step-by-step how to trigger it.
18
+ 3. **Conditions required**: What state must the contract be in?
19
+ 4. **Expected outcome**: What specific assertion will prove the vulnerability exists (and would fail if patched)?
20
 
21
+ Be concise and technical.
22
+ `.trim();
23
 
24
+ export const POC_INITIAL_PROMPT = `Based on your vulnerability analysis, generate a comprehensive Proof of Concept (PoC) test that demonstrates the vulnerability.
 
25
 
26
+ Sua missão: completar a função test_Exploit() no scaffold fornecido, seguindo o seu plano de análise.
 
27
 
28
+ ## REGRAS DE QUALIDADE
29
+ - A asserção final DEVE provar a vulnerabilidade.
30
+ - Use o mesmo estilo de imports e setup dos contratos de referência fornecidos.
31
+ - Output APENAS um bloco \`\`\`solidity ... \`\`\` com o arquivo completo.
32
+ `.trim();
33
 
34
+ export const POC_COMPILE_FIX_PROMPT = `The previous POC test failed to COMPILE. Please fix the compilation errors and regenerate the complete test file.
35
 
36
+ Focus strictly on:
37
+ - Fixing import statements and dependencies.
38
+ - Correcting Solidity syntax errors or missing members.
39
+ - Ensuring proper contract instantiation and function signatures.
40
+ - Resolving visibility issues.
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ Return the FULL corrected Solidity file in a \`\`\`solidity\`\`\` block.
43
+ `.trim();
 
 
 
 
44
 
45
+ export const POC_TEST_FIX_PROMPT = `The POC test compiled successfully but FAILED during execution (Revert or Assertion failure). Please fix the test logic and regenerate the complete test file.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ Focus strictly on:
48
+ - Correcting test logic and assertions to match the vulnerability.
49
+ - Fixing contract setup and initialization (realistic balances, roles).
50
+ - Ensuring proper exploit execution flow (e.g. correct order of calls).
51
+ - Verifying that the vulnerability demonstration is accurate and specific.
 
 
52
 
53
+ Return the FULL corrected Solidity file in a \`\`\`solidity\`\`\` block.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  `.trim();
src/agents/tester/state.ts CHANGED
@@ -14,6 +14,11 @@ export const PoCStateAnnotation = Annotation.Root({
14
  reducer: (_, y) => y, // overwrite — always latest version
15
  }),
16
 
 
 
 
 
 
17
  executionLogs: Annotation<string[]>({
18
  default: () => [],
19
  reducer: (x, y) => x.concat(y), // append — never lose previous logs
 
14
  reducer: (_, y) => y, // overwrite — always latest version
15
  }),
16
 
17
+ vulnerabilityAnalysis: Annotation<string>({
18
+ default: () => "",
19
+ reducer: (_, y) => y, // overwrite
20
+ }),
21
+
22
  executionLogs: Annotation<string[]>({
23
  default: () => [],
24
  reducer: (x, y) => x.concat(y), // append — never lose previous logs
src/agents/tester/tools/foundryRunner.ts CHANGED
@@ -4,7 +4,7 @@ import { writeFile, access } from "fs/promises";
4
  import { join } from "path";
5
 
6
  const execAsync = promisify(exec);
7
- const SANDBOX = process.env.SANDBOX_DIR || "/tmp/poc-sandbox";
8
  const TIMEOUT_MS = 60_000;
9
 
10
  export interface FoundryResult {
@@ -18,27 +18,36 @@ export interface FoundryResult {
18
  /**
19
  * Garante que o sandbox Foundry existe e está inicializado.
20
  */
21
- async function ensureSandbox() {
22
  try {
23
- await access(join(SANDBOX, "foundry.toml"));
24
  } catch {
25
- console.log("[foundryRunner] Sandbox não encontrado. Inicializando...");
26
  // Caminho absoluto para o script de setup (assume execução da raiz do projeto)
27
- await execAsync("./scripts/setup-sandbox.sh");
28
  }
29
  }
30
 
31
- export async function runFoundry(solidityCode: string): Promise<FoundryResult> {
32
- await ensureSandbox();
 
 
 
 
 
 
 
 
33
 
34
  // Escrever o arquivo no sandbox
35
- await writeFile(`${SANDBOX}/test/Exploit.t.sol`, solidityCode, "utf-8");
 
36
 
37
  try {
38
  const { stdout, stderr } = await execAsync(
39
  "forge test --match-contract ExploitTest -vvvv",
40
  {
41
- cwd: SANDBOX,
42
  timeout: TIMEOUT_MS,
43
  env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
44
  }
 
4
  import { join } from "path";
5
 
6
  const execAsync = promisify(exec);
7
+ const DEFAULT_SANDBOX = process.env.SANDBOX_DIR || "/tmp/poc-sandbox";
8
  const TIMEOUT_MS = 60_000;
9
 
10
  export interface FoundryResult {
 
18
  /**
19
  * Garante que o sandbox Foundry existe e está inicializado.
20
  */
21
+ async function ensureSandbox(sandboxDir: string) {
22
  try {
23
+ await access(join(sandboxDir, "foundry.toml"));
24
  } catch {
25
+ console.log(`[foundryRunner] Sandbox em ${sandboxDir} não encontrado. Inicializando...`);
26
  // Caminho absoluto para o script de setup (assume execução da raiz do projeto)
27
+ await execAsync("./scripts/setup-sandbox.sh", { env: { ...process.env, SANDBOX_DIR: sandboxDir } });
28
  }
29
  }
30
 
31
+ export async function runFoundry(solidityCode: string, sandboxDir: string = DEFAULT_SANDBOX): Promise<FoundryResult> {
32
+ await ensureSandbox(sandboxDir);
33
+
34
+ // Ensure test directory exists
35
+ const testDir = join(sandboxDir, "test");
36
+ try {
37
+ await access(testDir);
38
+ } catch {
39
+ await execAsync(`mkdir -p "${testDir}"`);
40
+ }
41
 
42
  // Escrever o arquivo no sandbox
43
+ const testPath = join(testDir, "Exploit.t.sol");
44
+ await writeFile(testPath, solidityCode, "utf-8");
45
 
46
  try {
47
  const { stdout, stderr } = await execAsync(
48
  "forge test --match-contract ExploitTest -vvvv",
49
  {
50
+ cwd: sandboxDir,
51
  timeout: TIMEOUT_MS,
52
  env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
53
  }
src/agents/tester/tools/scaffoldGenerator.ts CHANGED
@@ -3,27 +3,35 @@ import { VulnerabilityReport } from "../types.js";
3
  export function generateLocalScaffold(report: VulnerabilityReport): string {
4
  const cheatcodes = report.suggestedCheatcodes?.join(", ") ?? "vm.deal, vm.prank, vm.warp";
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  return `// SPDX-License-Identifier: UNLICENSED
7
- pragma solidity ^0.8.20;
8
 
9
  import "forge-std/Test.sol";
10
  import "forge-std/console.sol";
11
 
12
- // ── Código-fonte do contrato vulnerável ──────────────────────────────────────
13
- ${report.affectedContract.sourceCode}
14
- // ─────────────────────────────────────────────────────────────────────────────
15
 
16
  contract ExploitTest is Test {
17
  ${report.affectedContract.name} target;
18
  address constant ATTACKER = address(0xBEEF);
19
 
20
- // setUp() gerado automaticamente pelo Oracle NÃO MODIFICAR
21
- function setUp() public {
22
- target = new ${report.affectedContract.name}();
23
- vm.deal(address(target), 100 ether);
24
- vm.deal(ATTACKER, 10 ether);
25
- vm.label(address(target), "TARGET");
26
- vm.label(ATTACKER, "ATTACKER");
27
  }
28
 
29
  // Vulnerabilidade: ${report.title}
@@ -32,7 +40,7 @@ contract ExploitTest is Test {
32
  ${report.exploitablePaths ? `// Caminhos de Exploração:\n // - ${report.exploitablePaths.join("\n // - ")}` : ""}
33
  // Cheatcodes sugeridos: ${cheatcodes}
34
  //
35
- // COMPLETE APENAS ESTA FUNÇÃO não altere setUp() nem os campos acima
36
  function test_Exploit() public {
37
  vm.startPrank(ATTACKER);
38
  // TODO: implementar exploit aqui
 
3
  export function generateLocalScaffold(report: VulnerabilityReport): string {
4
  const cheatcodes = report.suggestedCheatcodes?.join(", ") ?? "vm.deal, vm.prank, vm.warp";
5
 
6
+ // Extrair pragma do código original para evitar conflitos de versão
7
+ const pragmaMatch = report.affectedContract.sourceCode.match(/pragma solidity ([^;]+);/);
8
+ const pragma = pragmaMatch ? pragmaMatch[0] : "pragma solidity ^0.8.20;";
9
+
10
+ let contractSetup = "";
11
+ if (report.affectedContract.sourceFilePath) {
12
+ // Se temos o caminho do arquivo, importamos ao invés de colar
13
+ contractSetup = `import { ${report.affectedContract.name} } from "../${report.affectedContract.sourceFilePath}";`;
14
+ } else {
15
+ // Fallback: colar o código (pode falhar por causa de imports ausentes no sandbox)
16
+ contractSetup = `// ── Código-fonte do contrato vulnerável ──────────────────────────────────────\n${report.affectedContract.sourceCode}\n// ─────────────────────────────────────────────────────────────────────────────`;
17
+ }
18
+
19
  return `// SPDX-License-Identifier: UNLICENSED
20
+ ${pragma}
21
 
22
  import "forge-std/Test.sol";
23
  import "forge-std/console.sol";
24
 
25
+ ${contractSetup}
 
 
26
 
27
  contract ExploitTest is Test {
28
  ${report.affectedContract.name} target;
29
  address constant ATTACKER = address(0xBEEF);
30
 
31
+ // setUp() - O Oracle tentou gerar um básico, mas sinta-se à vontade para ajustar se o contrato for complexo
32
+ function setUp() public virtual {
33
+ // target = new ${report.affectedContract.name}(...); // TODO: ajustar se necessário
34
+ vm.deal(ATTACKER, 100 ether);
 
 
 
35
  }
36
 
37
  // Vulnerabilidade: ${report.title}
 
40
  ${report.exploitablePaths ? `// Caminhos de Exploração:\n // - ${report.exploitablePaths.join("\n // - ")}` : ""}
41
  // Cheatcodes sugeridos: ${cheatcodes}
42
  //
43
+ // Implemente test_Exploit() e ajuste o setUp() se o contrato exigir argumentos no constructor.
44
  function test_Exploit() public {
45
  vm.startPrank(ATTACKER);
46
  // TODO: implementar exploit aqui
src/agents/tester/types.ts CHANGED
@@ -21,17 +21,23 @@ export interface VulnerabilityReport {
21
  description: string;
22
  affectedContract: {
23
  name: string;
24
- sourceCode: string; // Solidity completo, preferencialmente flattened
 
25
  };
26
  attackVector: string;
27
  suggestedCheatcodes?: string[];
28
  codeSnippet?: string;
29
  location?: string;
30
  exploitablePaths?: string[];
 
 
31
  }
32
 
33
  export interface OracleContext {
34
  solidityScaffold: string; // Exploit.t.sol parcial com setUp() pronto
 
 
 
35
  }
36
 
37
  export interface PoCResult {
 
21
  description: string;
22
  affectedContract: {
23
  name: string;
24
+ sourceCode: string;
25
+ sourceFilePath?: string;
26
  };
27
  attackVector: string;
28
  suggestedCheatcodes?: string[];
29
  codeSnippet?: string;
30
  location?: string;
31
  exploitablePaths?: string[];
32
+ customSandboxDir?: string; // Caminho para execução do Forge (opcional)
33
+ referenceTestCode?: string; // Código de um teste existente para referência de setup
34
  }
35
 
36
  export interface OracleContext {
37
  solidityScaffold: string; // Exploit.t.sol parcial com setUp() pronto
38
+ constructorInfo?: string; // Assinatura do constructor para ajudar no deploy
39
+ targetContractAPI?: string; // Resumo dos métodos e variáveis do contrato alvo
40
+ referenceTestHelpers?: string; // Resumo das funções auxiliares disponíveis no ambiente de teste
41
  }
42
 
43
  export interface PoCResult {
src/agents/tester/utils/logAnalyzer.ts CHANGED
@@ -23,8 +23,8 @@ export function analyzeFoundryLog(result: FoundryResult): LogAnalysis {
23
 
24
  if (result.combined.includes("Compiler run failed")) {
25
  const lines = result.combined.split("\n")
26
- .filter(l => l.includes("Error") || l.includes("error") || l.includes("-->"))
27
- .slice(0, 10);
28
  return {
29
  category: "compiler_error",
30
  summary: "Erro de compilação Solidity. Verifique: interfaces faltando, assinaturas incorretas, tipos incompatíveis.",
 
23
 
24
  if (result.combined.includes("Compiler run failed")) {
25
  const lines = result.combined.split("\n")
26
+ .filter(l => (l.includes("Error") || l.includes("error") || l.includes("-->")) && !l.includes("Warning"))
27
+ .slice(0, 30);
28
  return {
29
  category: "compiler_error",
30
  summary: "Erro de compilação Solidity. Verifique: interfaces faltando, assinaturas incorretas, tipos incompatíveis.",
src/agents/tester/utils/parserUtils.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as parser from "@solidity-parser/parser";
2
+
3
+ export interface ConstructorInfo {
4
+ parameters: string;
5
+ }
6
+
7
+ export function extractConstructor(sourceCode: string, contractName: string): ConstructorInfo | null {
8
+ try {
9
+ const ast = parser.parse(sourceCode, { range: true });
10
+ let constructorParams = "";
11
+ let found = false;
12
+
13
+ parser.visit(ast, {
14
+ ContractDefinition: (node) => {
15
+ if (node.name === contractName) {
16
+ for (const part of node.subNodes) {
17
+ if (part.type === "FunctionDefinition" && part.isConstructor) {
18
+ found = true;
19
+ if (part.range) {
20
+ constructorParams = sourceCode.slice(part.range[0], part.range[1]).split("{")[0].trim();
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ });
27
+
28
+ if (found) {
29
+ return {
30
+ parameters: constructorParams
31
+ };
32
+ }
33
+ } catch (e) {
34
+ // console.warn("Failed to parse Solidity for constructor:", e);
35
+ }
36
+ return null;
37
+ }
src/benchmark/runTesterBenchmark.ts ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { exec } from "child_process";
4
+ import { promisify } from "util";
5
+ import { runPoCGenerator } from "../agents/tester/index.js";
6
+ import { VulnerabilityReport } from "../agents/tester/types.js";
7
+ import "dotenv/config";
8
+
9
+ const execAsync = promisify(exec);
10
+ const DATASET_PATH = "Proof-of-Patch-only-dataset";
11
+ const METADATA_FILE = path.join(DATASET_PATH, "dataset_metadata.json");
12
+ const SUMMARY_FILE = "data/benchmark_summary.json";
13
+
14
+ /**
15
+ * Extracts the likely vulnerable file path from annotation text.
16
+ * Looks for paths ending in .sol or github links.
17
+ */
18
+ /**
19
+ * Extracts the likely vulnerable file path from annotation text.
20
+ */
21
+ function extractVulnerableFilePath(text: string): string | null {
22
+ // Matches GitHub blob links: /blob/branch/path/to/File.sol
23
+ const githubBlobRegex = /\/blob\/[^/]+\/([^#\s]+\.sol)/g;
24
+ let match;
25
+ if ((match = githubBlobRegex.exec(text)) !== null) {
26
+ return match[1];
27
+ }
28
+
29
+ // Fallback to general .sol paths
30
+ const solPathRegex = /(?:^|[\s])([a-zA-Z0-9._/-]+\.sol)(?:#L\d+)?/g;
31
+ const paths: string[] = [];
32
+ while ((match = solPathRegex.exec(text)) !== null) {
33
+ const p = match[1];
34
+ if (!p.includes("test/") && !p.includes("Test.sol")) {
35
+ paths.push(p);
36
+ }
37
+ }
38
+
39
+ // Prioritize paths containing "src"
40
+ const srcPath = paths.find(p => p.includes("src/"));
41
+ return srcPath || (paths.length > 0 ? paths[0] : null);
42
+ }
43
+
44
+ /**
45
+ * Recursively finds a file by name within a directory, prioritizing src/
46
+ */
47
+ async function findFileRecursively(dir: string, fileName: string): Promise<string | null> {
48
+ const entries = await fs.readdir(dir, { withFileTypes: true });
49
+ const subdirs: string[] = [];
50
+
51
+ // Check files in current dir first
52
+ for (const entry of entries) {
53
+ const fullPath = path.join(dir, entry.name);
54
+ if (entry.isFile() && entry.name === fileName) {
55
+ return fullPath;
56
+ }
57
+ if (entry.isDirectory() && entry.name !== "lib" && entry.name !== "node_modules") {
58
+ subdirs.push(fullPath);
59
+ }
60
+ }
61
+
62
+ // Prioritize "src" subdirectory if it exists
63
+ const srcDir = subdirs.find(d => path.basename(d) === "src");
64
+ if (srcDir) {
65
+ const found = await findFileRecursively(srcDir, fileName);
66
+ if (found) return found;
67
+ }
68
+
69
+ // Check other subdirs
70
+ for (const subdir of subdirs) {
71
+ if (path.basename(subdir) === "src") continue; // Already checked
72
+ const found = await findFileRecursively(subdir, fileName);
73
+ if (found) return found;
74
+ }
75
+
76
+ // Fallback to lib if nothing else found
77
+ const libDir = entries.find(e => e.isDirectory() && e.name === "lib");
78
+ if (libDir) {
79
+ return findFileRecursively(path.join(dir, "lib"), fileName);
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ async function main() {
86
+ const metadataContent = await fs.readFile(METADATA_FILE, "utf-8");
87
+ const metadata = JSON.parse(metadataContent);
88
+ const findingsIds = Object.keys(metadata);
89
+
90
+ const limit = process.argv[2] ? parseInt(process.argv[2]) : findingsIds.length;
91
+
92
+ console.log(`[Benchmark] Starting evaluation (Total available: ${findingsIds.length}, Limit: ${limit})...`);
93
+
94
+ const results: any[] = [];
95
+ let processedCount = 0;
96
+
97
+ for (const id of findingsIds) {
98
+ if (processedCount >= limit) break;
99
+
100
+ const finding = metadata[id];
101
+
102
+ if (finding.benchmark_results?.vuln_status === "success" && !process.env.FORCE_RERUN) {
103
+ console.log(`[${id}] Skipping: already successful.`);
104
+ processedCount++;
105
+ continue;
106
+ }
107
+
108
+ console.log(`\n--- [${id}] ${finding.repo_name} ---`);
109
+ processedCount++;
110
+
111
+ try {
112
+ const annotationPath = path.join(DATASET_PATH, finding.annotations);
113
+ let annotationText = "";
114
+ try {
115
+ annotationText = await fs.readFile(annotationPath, "utf-8");
116
+ } catch (e) {
117
+ console.warn(`[${id}] Annotation file not found at ${annotationPath}`);
118
+ }
119
+
120
+ const targetDir = path.join(process.cwd(), DATASET_PATH, finding.target_directory);
121
+
122
+ // STEP 1: Accurate Source Code Resolution
123
+ const extractedPath = extractVulnerableFilePath(annotationText);
124
+ let mainContractPath = "";
125
+ let relativeContractPath = finding.main_contract;
126
+
127
+ if (extractedPath) {
128
+ const directPath = path.join(targetDir, extractedPath);
129
+ try {
130
+ await fs.access(directPath);
131
+ mainContractPath = directPath;
132
+ relativeContractPath = extractedPath;
133
+ } catch {
134
+ const fileName = path.basename(extractedPath);
135
+ console.log(`[${id}] File not found at ${extractedPath}, searching for ${fileName} recursively...`);
136
+ const foundPath = await findFileRecursively(targetDir, fileName);
137
+ if (foundPath) {
138
+ mainContractPath = foundPath;
139
+ relativeContractPath = path.relative(targetDir, foundPath);
140
+ }
141
+ }
142
+ }
143
+
144
+ if (!mainContractPath) {
145
+ mainContractPath = path.join(targetDir, finding.main_contract);
146
+ relativeContractPath = finding.main_contract;
147
+ }
148
+
149
+ console.log(`[${id}] Using source file: ${relativeContractPath}`);
150
+
151
+ let sourceCode = "";
152
+ try {
153
+ sourceCode = await fs.readFile(mainContractPath, "utf-8");
154
+ } catch (e) {
155
+ console.warn(`[${id}] Contract not found at ${mainContractPath}, falling back to metadata.main_contract`);
156
+ try {
157
+ sourceCode = await fs.readFile(path.join(targetDir, finding.main_contract), "utf-8");
158
+ } catch (e2) {
159
+ throw new Error(`Could not find any source code for ${id}`);
160
+ }
161
+ }
162
+
163
+ const tempVulnDir = path.join(process.cwd(), "temp_vuln_run", id);
164
+ console.log(`[${id}] Preparing isolated sandbox at ${tempVulnDir}...`);
165
+ await execAsync(`mkdir -p temp_vuln_run && rm -rf ${tempVulnDir} && cp -r ${targetDir} ${tempVulnDir}`);
166
+ await execAsync(`rm -f ${tempVulnDir}/.git`);
167
+
168
+ // STEP 1.5: Reference Test Resolution
169
+ let referenceTestCode = "";
170
+ if (finding.test_fix_commands) {
171
+ const match = finding.test_fix_commands.match(/--match-path\s+([^\s]+)/);
172
+ if (match) {
173
+ const testPath = path.join(targetDir, match[1]);
174
+ try {
175
+ referenceTestCode = await fs.readFile(testPath, "utf-8");
176
+ console.log(`[${id}] Found reference test at ${match[1]}`);
177
+ } catch {
178
+ console.warn(`[${id}] Could not read reference test at ${testPath}`);
179
+ }
180
+ }
181
+ }
182
+
183
+ const report: VulnerabilityReport = {
184
+ id: id,
185
+ title: `${finding.repo_name} - ${id}`,
186
+ severity: (finding.impact?.toLowerCase() || "medium") as any,
187
+ type: finding.expected_vulnerability || "unknown",
188
+ description: annotationText,
189
+ referenceTestCode: referenceTestCode,
190
+ affectedContract: {
191
+ name: relativeContractPath.split("/").pop()!.replace(".sol", ""),
192
+ sourceCode: sourceCode,
193
+ sourceFilePath: relativeContractPath
194
+ },
195
+ attackVector: "Vulnerability analysis from dataset annotations.",
196
+ customSandboxDir: tempVulnDir
197
+ };
198
+
199
+ console.log(`[${id}] Generating PoC and running on VULNERABLE version...`);
200
+ const resultVuln = await runPoCGenerator(report);
201
+
202
+ if (process.env.DEBUG_CONTEXT === "true") {
203
+ console.log("\n" + "=".repeat(20) + " GENERATED POC START " + "=".repeat(20));
204
+ console.log(resultVuln.solidityCode);
205
+ console.log("=".repeat(20) + " GENERATED POC END " + "=".repeat(20) + "\n");
206
+ }
207
+
208
+ let statusPatch = "not_tested";
209
+
210
+ if (resultVuln.status === "success") {
211
+ console.log(`[${id}] Running PoC on PATCHED version to verify specificity...`);
212
+
213
+ const tempPatchDir = path.join(process.cwd(), "temp_patch_run", id);
214
+ try {
215
+ await execAsync(`mkdir -p temp_patch_run && rm -rf ${tempPatchDir} && cp -r ${targetDir} ${tempPatchDir}`);
216
+ await execAsync(`rm -f ${tempPatchDir}/.git`);
217
+
218
+ const patchSourceDir = path.join(process.cwd(), DATASET_PATH, finding.patch);
219
+ await execAsync(`cp -rv ${patchSourceDir}/* ${tempPatchDir}/ || true`);
220
+
221
+ const { runFoundry } = await import("../agents/tester/tools/foundryRunner.js");
222
+ const patchExec = await runFoundry(resultVuln.solidityCode, tempPatchDir);
223
+
224
+ const passedOnPatch = patchExec.exitCode === 0 && patchExec.stdout.includes("ok");
225
+ statusPatch = passedOnPatch ? "success" : "failed";
226
+
227
+ if (statusPatch === "failed") {
228
+ await execAsync(`rm -rf ${tempPatchDir}`);
229
+ }
230
+ } catch (e: any) {
231
+ console.error(`[${id}] Patch run error:`, e.message);
232
+ statusPatch = "error";
233
+ }
234
+ }
235
+
236
+ const reproducible = resultVuln.status === "success";
237
+ const specific = resultVuln.status === "success" && statusPatch === "failed";
238
+
239
+ finding.benchmark_results = {
240
+ vuln_status: resultVuln.status,
241
+ patch_status: statusPatch,
242
+ reproducibility: reproducible,
243
+ specificity: specific,
244
+ iterations: resultVuln.iterations,
245
+ timestamp: new Date().toISOString(),
246
+ };
247
+
248
+ if (resultVuln.status === "failed") {
249
+ finding.benchmark_results.last_vuln_error = resultVuln.executionLogs[resultVuln.executionLogs.length - 1]?.slice(0, 500);
250
+ }
251
+
252
+ results.push({
253
+ id,
254
+ reproducible,
255
+ specific,
256
+ iterations: resultVuln.iterations
257
+ });
258
+
259
+ await fs.writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2));
260
+ console.log(`[${id}] Result: Reproducible=${reproducible}, Specific=${specific}`);
261
+
262
+ } catch (err: any) {
263
+ console.error(`[${id}] Fatal Error:`, err.message);
264
+ finding.benchmark_results = {
265
+ status: "error",
266
+ error: err.message,
267
+ timestamp: new Date().toISOString()
268
+ };
269
+ await fs.writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2));
270
+ }
271
+ }
272
+
273
+ const total = results.length;
274
+ const reproCount = results.filter(r => r.reproducible).length;
275
+ const specCount = results.filter(r => r.specific).length;
276
+ const avgIter = total > 0 ? results.reduce((acc, r) => acc + r.iterations, 0) / total : 0;
277
+
278
+ const summary = {
279
+ timestamp: new Date().toISOString(),
280
+ total_processed: total,
281
+ reproducibility_rate: total > 0 ? (reproCount / total) * 100 : 0,
282
+ specificity_rate: reproCount > 0 ? (specCount / reproCount) * 100 : 0,
283
+ overall_ground_truth_rate: total > 0 ? (specCount / total) * 100 : 0,
284
+ average_iterations: avgIter
285
+ };
286
+
287
+ console.log("\n" + "=".repeat(50));
288
+ console.log("BENCHMARK SUMMARY");
289
+ console.log("=".repeat(50));
290
+ console.log(`Total Findings: ${total}`);
291
+ console.log(`Reproducibility: ${summary.reproducibility_rate.toFixed(1)}% (${reproCount}/${total})`);
292
+ console.log(`Specificity: ${summary.specificity_rate.toFixed(1)}% (${specCount}/${reproCount})`);
293
+ console.log(`Overall Success: ${summary.overall_ground_truth_rate.toFixed(1)}% (Verified Ground Truth)`);
294
+ console.log(`Avg Iterations: ${avgIter.toFixed(2)}`);
295
+ console.log("=".repeat(50));
296
+
297
+ await fs.mkdir(path.dirname(SUMMARY_FILE), { recursive: true });
298
+ await fs.writeFile(SUMMARY_FILE, JSON.stringify({ summary, details: results }, null, 2));
299
+ console.log(`Summary saved to ${SUMMARY_FILE}`);
300
+ }
301
+
302
+ main().catch(console.error);