Tales-Cunha commited on
Commit
086d1ff
·
1 Parent(s): 6389903

feat: Validated Implementation with Centrifuge Case

Browse files

SYSTEM_PROMPT: Updated with expert guidelines (explainability, quality,
iterative refinement)

scripts/setup-sandbox.sh ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ SANDBOX="/tmp/poc-sandbox"
5
+ FORGE_BIN="$HOME/.foundry/bin/forge"
6
+
7
+ echo "Inicializando sandbox Foundry em $SANDBOX..."
8
+ rm -rf "$SANDBOX"
9
+ mkdir -p "$SANDBOX"
10
+ cd "$SANDBOX"
11
+
12
+ # Iniciar projeto forge mínimo sem git
13
+ "$FORGE_BIN" init --no-git --quiet
14
+
15
+ # Limpar arquivos padrão que causam erros de importação se deletados parcialmente
16
+ rm -rf src/*
17
+ rm -rf test/*
18
+ rm -rf script/*
19
+
20
+ # Criar foundry.toml configurado
21
+ cat > foundry.toml << 'EOF'
22
+ [profile.default]
23
+ src = "src"
24
+ test = "test"
25
+ script = "script"
26
+ out = "out"
27
+ libs = ["lib"]
28
+ solc-version = "0.8.20"
29
+ optimizer = true
30
+ optimizer_runs = 200
31
+ EOF
32
+
33
+ echo "Sandbox pronto. Testando com forge build..."
34
+ "$FORGE_BIN" build
35
+ echo "OK — sandbox funcionando em $SANDBOX"
src/agents/tester/agent.ts CHANGED
@@ -1,7 +1,21 @@
 
1
  import { StateGraph, END, START } from "@langchain/langgraph";
2
  import { PoCStateAnnotation, PoCState } from "./state.js";
3
  import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
4
  import { OracleContext } from "./types.js";
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
7
  console.log("[oracleNode] gerando scaffold para:", state.report.title);
@@ -14,18 +28,93 @@ async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
14
  }
15
 
16
  async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
17
- console.log("[generatePoCNode] stub iteração:", state.iterations);
18
- return { iterations: 1 };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
 
21
  async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
22
- console.log("[runFoundryNode] stub");
23
- return { status: "success" };
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
  async function reflectNode(state: PoCState): Promise<Partial<PoCState>> {
27
- console.log("[reflectNode] stub");
28
- return {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
  const graph = new StateGraph(PoCStateAnnotation)
@@ -36,7 +125,10 @@ const graph = new StateGraph(PoCStateAnnotation)
36
  .addEdge(START, "oracleNode")
37
  .addEdge("oracleNode", "generatePoCNode")
38
  .addEdge("generatePoCNode", "runFoundryNode")
39
- .addEdge("runFoundryNode", "reflectNode")
40
- .addEdge("reflectNode", END);
 
 
 
41
 
42
  export const testerAgent = graph.compile();
 
1
+ import "dotenv/config";
2
  import { StateGraph, END, START } from "@langchain/langgraph";
3
  import { PoCStateAnnotation, PoCState } from "./state.js";
4
  import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
5
  import { OracleContext } from "./types.js";
6
+ import { ChatOpenRouter } from "@langchain/openrouter";
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 = new ChatOpenRouter({
15
+ model: "deepseek/deepseek-v4-flash",
16
+ temperature: 0.2,
17
+ apiKey: process.env.OPENROUTER_API_KEY,
18
+ });
19
 
20
  async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
21
  console.log("[oracleNode] gerando scaffold para:", state.report.title);
 
28
  }
29
 
30
  async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
31
+ const { report, oracleContext, executionLogs, pocCode, iterations, lastError } = state;
32
+ const isRetry = iterations > 0;
33
+
34
+ const userMessage = isRetry
35
+ ? `O seguinte exploit FALHOU no Foundry.
36
+
37
+ Código anterior:
38
+ \`\`\`solidity
39
+ ${pocCode}
40
+ \`\`\`
41
+
42
+ Output do Forge (última execução):
43
+ ${executionLogs[executionLogs.length - 1]?.slice(0, 3000) ?? "sem logs"}
44
+
45
+ Análise do erro: ${lastError ?? "desconhecido"}
46
+
47
+ Corrija o código. Retorne o arquivo Solidity completo corrigido.`
48
+ : `Relatório de Vulnerabilidade:
49
+ - Título: ${report.title}
50
+ - Tipo: ${report.type}
51
+ - Descrição: ${report.description}
52
+ - Vetor de Ataque: ${report.attackVector}
53
+ ${report.exploitablePaths ? `- Caminhos de Exploração:\n * ${report.exploitablePaths.join("\n * ")}` : ""}
54
+
55
+ Scaffold (complete APENAS test_Exploit):
56
+ \`\`\`solidity
57
+ ${oracleContext!.solidityScaffold}
58
+ \`\`\``;
59
+
60
+ console.log(`[generatePoCNode] iteração ${iterations + 1}, isRetry=${isRetry}`);
61
+
62
+ try {
63
+ const response = await llm.invoke([
64
+ { role: "system", content: SYSTEM_PROMPT },
65
+ { role: "user", content: userMessage },
66
+ ]);
67
+ const solidityCode = extractSolidity(response.content as string);
68
+ console.log("[generatePoCNode] Solidity extraído, tamanho:", solidityCode.length);
69
+ return { pocCode: solidityCode, iterations: 1 };
70
+ } catch (err) {
71
+ console.error("[generatePoCNode] falha:", (err as Error).message);
72
+ return { iterations: 1, lastError: `Erro na geração/extração: ${(err as Error).message}` };
73
+ }
74
  }
75
 
76
  async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
77
+ const result = await runFoundry(state.pocCode);
78
+ const analysis = analyzeFoundryLog(result);
79
+ const passed = result.exitCode === 0 && result.stdout.includes("ok");
80
+
81
+ console.log(`[runFoundryNode] exitCode=${result.exitCode}, passed=${passed}`);
82
+
83
+ return {
84
+ executionLogs: [result.combined], // reducer append
85
+ lastError: analysis.summary,
86
+ status: passed ? "success"
87
+ : result.timedOut ? "timeout"
88
+ : "running",
89
+ };
90
  }
91
 
92
  async function reflectNode(state: PoCState): Promise<Partial<PoCState>> {
93
+ const lastLog = state.executionLogs[state.executionLogs.length - 1];
94
+ if (!lastLog) {
95
+ return { lastError: "Sem logs disponíveis para análise." };
96
+ }
97
+
98
+ const mockResult = {
99
+ exitCode: 1, timedOut: lastLog.includes("TIMEOUT"),
100
+ stdout: "", stderr: "", combined: lastLog,
101
+ };
102
+
103
+ const analysis = analyzeFoundryLog(mockResult as any);
104
+
105
+ console.log(`[reflectNode] categoria: ${analysis.category}`);
106
+ console.log(`[reflectNode] resumo: ${analysis.summary}`);
107
+
108
+ return {
109
+ lastError: `[${analysis.category.toUpperCase()}] ${analysis.summary}\n\nLinhas relevantes:\n${analysis.relevantLines.join("\n")}`,
110
+ };
111
+ }
112
+
113
+ function routeAfterFoundry(state: PoCState): "reflectNode" | typeof END {
114
+ if (state.status === "success") return END;
115
+ if (state.status === "timeout") return END;
116
+ if (state.iterations >= MAX_ITERATIONS) return END;
117
+ return "reflectNode";
118
  }
119
 
120
  const graph = new StateGraph(PoCStateAnnotation)
 
125
  .addEdge(START, "oracleNode")
126
  .addEdge("oracleNode", "generatePoCNode")
127
  .addEdge("generatePoCNode", "runFoundryNode")
128
+ .addConditionalEdges("runFoundryNode", routeAfterFoundry, {
129
+ reflectNode: "reflectNode",
130
+ [END]: END,
131
+ })
132
+ .addEdge("reflectNode", "generatePoCNode");
133
 
134
  export const testerAgent = graph.compile();
src/agents/tester/data/input_centrifuge.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "title": "Escrow mismatch in LiquidityPool due to price changes during epoch execution",
3
+ "description": "The LiquidityPool contract relies on an external InvestmentManager to process deposits and mints. When an investor requests a deposit, their assets are locked. During the epoch execution, if the tranche token price changes significantly, the amount of shares to be minted (TokenShares) may exceed the available balance in the Escrow contract, causing subsequent collection transactions (mint/deposit) to revert for some users while others succeed.",
4
+ "recommendation": "Ensure the Escrow contract is always sufficiently funded by validating price impacts before final execution or implement a more robust collection mechanism that handles partial fills or explicit failure states when Escrow is empty.",
5
+ "severity": "high",
6
+ "codeSnippet": "function deposit(uint256 assets, address receiver) public withApproval(receiver) returns (uint256 shares) {\n shares = investmentManager.processDeposit(receiver, assets);\n emit Deposit(address(this), receiver, assets, shares);\n}",
7
+ "location": "L148-L151",
8
+ "path": "contracts/LiquidityPool.sol",
9
+ "judgeReview": {
10
+ "review": "Confirmed valid finding. The vulnerability occurs when multiple investors deposit at different prices within the same logic flow. If the price in the second epoch is higher/lower than expected, the calculation of total shares needed in Escrow might be incorrect, leading to a denial of service (revert) for users trying to collect their shares after the price update.",
11
+ "confidence": 0.95,
12
+ "exploitablePaths": [
13
+ "User A deposits 100 assets at price 1.25 -> User B deposits 100 assets at price 2.0 -> Price updates -> User A collects successfully -> User B tries to collect but Escrow is empty -> Transaction reverts."
14
+ ]
15
+ }
16
+ }
src/agents/tester/prompts/system.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const SYSTEM_PROMPT = `Você é um Especialista em Testes de Segurança de Smart Contracts. Sua missão é gerar exploits Proof-of-Concept (PoC) executáveis que demonstrem vulnerabilidades usando Foundry.
2
+
3
+ ## DIRETRIZES DE EXPLICABILIDADE
4
+ - Escreva exploits que provem claramente a vulnerabilidade.
5
+ - Inclua comentários detalhados documentando cada passo do ataque.
6
+ - O PoC deve ser autoexplicativo para auditores de segurança.
7
+
8
+ ## DIRETRIZES TÉCNICAS (FOUNDRY)
9
+ - Use o framework Foundry exclusivamente.
10
+ - NÃO modifique o contrato original ou o bloco "setUp()" fornecido no scaffold.
11
+ - Utilize cheatcodes de forma apropriada: vm.prank(), vm.deal(), vm.warp(), vm.expectRevert().
12
+ - A assertion final DEVE usar assertTrue(), assertGt() ou assertEq() para provar o sucesso do exploit.
13
+
14
+ ## EXECUTABILIDADE E QUALIDADE
15
+ - Garanta que o código compila com a versão de Solidity especificada.
16
+ - Mantenha o PoC minimalista e focado apenas na vulnerabilidade descrita.
17
+ - Se necessário, crie contratos auxiliares (ex: atacante malicioso) ANTES do contrato ExploitTest.
18
+ - Preserve a lógica original do contrato sem modificações.
19
+
20
+ ## REFINAMENTO ITERATIVO
21
+ - Se o código falhar, analise os logs do Foundry para identificar se o erro é de COMPILAÇÃO ou de LÓGICA (revert inesperado, assertion falhou).
22
+ - Para erros de importação, use apenas os arquivos já presentes no projeto.
23
+ - Se travar no mesmo erro por >3 iterações, tente uma abordagem mais simples que ainda prove o ponto.
24
+
25
+ ## FORMATO DE OUTPUT
26
+ Retorne APENAS um bloco de código Solidity completo:
27
+ \`\`\`solidity
28
+ // Código aqui
29
+ \`\`\`
30
+ `.trim();
src/agents/tester/tools/foundryRunner.ts ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { exec } from "child_process";
2
+ import { promisify } from "util";
3
+ import { writeFile } from "fs/promises";
4
+
5
+ const execAsync = promisify(exec);
6
+ const SANDBOX = "/tmp/poc-sandbox";
7
+ const TIMEOUT_MS = 60_000;
8
+
9
+ export interface FoundryResult {
10
+ exitCode: number;
11
+ stdout: string;
12
+ stderr: string;
13
+ combined: string;
14
+ timedOut: boolean;
15
+ }
16
+
17
+ export async function runFoundry(solidityCode: string): Promise<FoundryResult> {
18
+ // Escrever o arquivo no sandbox
19
+ await writeFile(`${SANDBOX}/test/Exploit.t.sol`, solidityCode, "utf-8");
20
+
21
+ try {
22
+ const { stdout, stderr } = await execAsync(
23
+ "forge test --match-contract ExploitTest -vvvv",
24
+ {
25
+ cwd: SANDBOX,
26
+ timeout: TIMEOUT_MS,
27
+ env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
28
+ }
29
+ );
30
+ return {
31
+ exitCode: 0,
32
+ stdout,
33
+ stderr,
34
+ combined: `STDOUT:\n${stdout}\nSTDERR:\n${stderr}`,
35
+ timedOut: false,
36
+ };
37
+ } catch (err: any) {
38
+ if (err.killed || err.signal === "SIGTERM") {
39
+ return {
40
+ exitCode: -1, stdout: "", stderr: "Forge timed out",
41
+ combined: `TIMEOUT após ${TIMEOUT_MS / 1000}s`,
42
+ timedOut: true,
43
+ };
44
+ }
45
+ return {
46
+ exitCode: err.code ?? 1,
47
+ stdout: err.stdout ?? "",
48
+ stderr: err.stderr ?? "",
49
+ combined: `STDOUT:\n${err.stdout ?? ""}\nSTDERR:\n${err.stderr ?? ""}`,
50
+ timedOut: false,
51
+ };
52
+ }
53
+ }
src/agents/tester/utils/extractSolidity.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function extractSolidity(llmOutput: string): string {
2
+ // Caso 1: bloco ```solidity ... ``` padrão
3
+ const match = llmOutput.match(/```solidity\s*([\s\S]*?)```/);
4
+ if (match) return match[1].trim();
5
+
6
+ // Caso 2: LLM omitiu backticks mas começa com pragma/SPDX
7
+ const trimmed = llmOutput.trim();
8
+ if (trimmed.startsWith("// SPDX") || trimmed.startsWith("pragma")) {
9
+ return trimmed;
10
+ }
11
+
12
+ // Caso 3: output inválido — lançar erro descritivo
13
+ throw new Error(
14
+ `LLM output não contém bloco Solidity válido. Preview: "${llmOutput.slice(0, 200)}"`
15
+ );
16
+ }
src/agents/tester/utils/logAnalyzer.ts ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { FoundryResult } from "../tools/foundryRunner.js";
2
+
3
+ export type ErrorCategory =
4
+ | "compiler_error"
5
+ | "revert_no_message"
6
+ | "revert_with_message"
7
+ | "assertion_failed"
8
+ | "timeout"
9
+ | "unknown";
10
+
11
+ export interface LogAnalysis {
12
+ category: ErrorCategory;
13
+ summary: string; // 1-2 frases em linguagem natural para o LLM
14
+ relevantLines: string[]; // máx 10 linhas do log original
15
+ }
16
+
17
+ export function analyzeFoundryLog(result: FoundryResult): LogAnalysis {
18
+ if (result.timedOut) return {
19
+ category: "timeout",
20
+ summary: "Forge excedeu 60s. O exploit pode ter entrado em loop infinito ou a lógica está bloqueante.",
21
+ relevantLines: [],
22
+ };
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.",
31
+ relevantLines: lines,
32
+ };
33
+ }
34
+
35
+ if (result.combined.includes("FAIL")) {
36
+ const revertReason = result.combined.match(/revert: (.+)/)?.[1];
37
+ const assertionFail = result.combined.includes("Assertion Failed") || result.combined.includes("assertion failed");
38
+
39
+ if (assertionFail) return {
40
+ category: "assertion_failed",
41
+ summary: "O exploit executou mas a assertion final falhou — o atacante não obteve o resultado esperado.",
42
+ relevantLines: result.combined.split("\n")
43
+ .filter(l => l.includes("assertion") || l.includes("FAIL")).slice(0, 10),
44
+ };
45
+
46
+ if (revertReason) return {
47
+ category: "revert_with_message",
48
+ summary: `Transação reverteu com: "${revertReason}". O contrato rejeitou a operação.`,
49
+ relevantLines: [revertReason],
50
+ };
51
+
52
+ return {
53
+ category: "revert_no_message",
54
+ summary: "Transação reverteu sem mensagem. Verifique a ordem das chamadas, permissões e estado do contrato.",
55
+ relevantLines: result.combined.split("\n")
56
+ .filter(l => l.includes("revert") || l.includes("FAIL")).slice(0, 5),
57
+ };
58
+ }
59
+
60
+ return {
61
+ category: "unknown",
62
+ summary: "Erro desconhecido. Revisar output completo do forge.",
63
+ relevantLines: result.combined.split("\n").slice(0, 10),
64
+ };
65
+ }
tests/centrifuge_flat.sol ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ pragma solidity 0.8.21;
3
+
4
+ interface IERC20 {
5
+ function totalSupply() external view returns (uint256);
6
+ function balanceOf(address account) external view returns (uint256);
7
+ function transfer(address recipient, uint256 amount) external returns (bool);
8
+ function allowance(address owner, address spender) external view returns (uint256);
9
+ function approve(address spender, uint256 amount) external returns (bool);
10
+ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
11
+ }
12
+
13
+ interface IERC4626 is IERC20 {
14
+ function asset() external view returns (address);
15
+ }
16
+
17
+ interface InvestmentManagerLike {
18
+ function processDeposit(address receiver, uint256 assets) external returns (uint256);
19
+ function processMint(address receiver, uint256 shares) external returns (uint256);
20
+ function maxDeposit(address user, address _tranche) external view returns (uint256);
21
+ function maxMint(address user, address _tranche) external view returns (uint256);
22
+ function requestDeposit(uint256 assets, address receiver) external;
23
+ }
24
+
25
+ contract Auth {
26
+ mapping (address => uint) public wards;
27
+ function rely(address usr) external auth { wards[usr] = 1; }
28
+ function deny(address usr) external auth { wards[usr] = 0; }
29
+ modifier auth {
30
+ require(wards[msg.sender] == 1, "not-authorized");
31
+ _;
32
+ }
33
+ }
34
+
35
+ contract LiquidityPool is Auth {
36
+ uint64 public poolId;
37
+ bytes16 public trancheId;
38
+ address public immutable asset;
39
+ address public immutable share;
40
+ InvestmentManagerLike public investmentManager;
41
+
42
+ constructor(uint64 poolId_, bytes16 trancheId_, address asset_, address share_, address investmentManager_) {
43
+ poolId = poolId_;
44
+ trancheId = trancheId_;
45
+ asset = asset_;
46
+ share = share_;
47
+ investmentManager = InvestmentManagerLike(investmentManager_);
48
+ wards[msg.sender] = 1;
49
+ }
50
+
51
+ modifier withApproval(address owner) {
52
+ require(msg.sender == owner, "LiquidityPool/no-approval");
53
+ _;
54
+ }
55
+
56
+ function deposit(uint256 assets, address receiver) public withApproval(receiver) returns (uint256 shares) {
57
+ shares = investmentManager.processDeposit(receiver, assets);
58
+ }
59
+
60
+ function mint(uint256 shares, address receiver) public withApproval(receiver) returns (uint256 assets) {
61
+ assets = investmentManager.processMint(receiver, shares);
62
+ }
63
+
64
+ function maxDeposit(address receiver) public view returns (uint256) {
65
+ return investmentManager.maxDeposit(receiver, address(this));
66
+ }
67
+
68
+ function maxMint(address receiver) external view returns (uint256 maxShares) {
69
+ return investmentManager.maxMint(receiver, address(this));
70
+ }
71
+ }
tests/run-centrifuge-test.ts ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { testerAgent } from "../src/agents/tester/agent.js";
2
+ import { Finding, VulnerabilityReport } from "../src/agents/tester/types.js";
3
+ import { readFileSync } from "fs";
4
+
5
+ function mapFindingToReport(finding: Finding, sourceCode: string): VulnerabilityReport {
6
+ const nameMatch = finding.path.match(/([^\/]+)\.sol$/);
7
+ const contractName = nameMatch ? nameMatch[1] : "TargetContract";
8
+
9
+ return {
10
+ id: finding.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50),
11
+ severity: finding.severity === "high" ? "high" : finding.severity === "medium" ? "medium" : "low",
12
+ type: "custom",
13
+ title: finding.title,
14
+ description: finding.description,
15
+ affectedContract: {
16
+ name: contractName,
17
+ sourceCode: sourceCode,
18
+ },
19
+ attackVector: finding.judgeReview.exploitablePaths[0] || "Unknown vector",
20
+ exploitablePaths: finding.judgeReview.exploitablePaths,
21
+ codeSnippet: finding.codeSnippet,
22
+ location: finding.location
23
+ };
24
+ }
25
+
26
+ async function main() {
27
+ const input = JSON.parse(readFileSync("src/agents/tester/data/input_centrifuge.json", "utf-8"));
28
+ const sourceCode = readFileSync("tests/centrifuge_flat.sol", "utf-8");
29
+
30
+ const report = mapFindingToReport(input, sourceCode);
31
+
32
+ console.log("Iniciando execução do Agente Tester com Centrifuge Trajectory 008...");
33
+ const result = await testerAgent.invoke({ report });
34
+
35
+ console.log("\n======= Resultado =======");
36
+ console.log("Status Final:", result.status);
37
+ console.log("Iterações:", result.iterations);
38
+ if (result.lastError) console.log("Último Erro:", result.lastError);
39
+
40
+ console.log("\n======= Código Gerado =======");
41
+ console.log(result.pocCode);
42
+ }
43
+
44
+ main().catch(console.error);
tests/run-input-test.ts ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { testerAgent } from "../src/agents/tester/agent.js";
2
+ import { Finding, VulnerabilityReport } from "../src/agents/tester/types.js";
3
+ import { readFileSync } from "fs";
4
+
5
+ function mapFindingToReport(finding: Finding, sourceCode: string): VulnerabilityReport {
6
+ const nameMatch = finding.path.match(/([^\/]+)\.sol$/);
7
+ const contractName = nameMatch ? nameMatch[1] : "TargetContract";
8
+
9
+ return {
10
+ id: finding.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50),
11
+ severity: finding.severity === "high" ? "high" : finding.severity === "medium" ? "medium" : "low",
12
+ type: "custom",
13
+ title: finding.title,
14
+ description: finding.description,
15
+ affectedContract: {
16
+ name: contractName,
17
+ sourceCode: sourceCode,
18
+ },
19
+ attackVector: finding.judgeReview.exploitablePaths[0] || "Unknown vector",
20
+ exploitablePaths: finding.judgeReview.exploitablePaths,
21
+ codeSnippet: finding.codeSnippet,
22
+ location: finding.location
23
+ };
24
+ }
25
+
26
+ async function main() {
27
+ const input = JSON.parse(readFileSync("src/agents/tester/data/input.json", "utf-8"));
28
+
29
+ // O Finding do auditor já tem o 'codeSnippet', mas para o Oracle precisamos do 'sourceCode' completo.
30
+ // Como não temos o repositório do coder aqui, vamos usar o codeSnippet envolto em um contrato mínimo
31
+ // ou assumir que o codeSnippet é representativo para o teste.
32
+ // Na vida real, o index.ts passa o coderResult.contract.
33
+
34
+ // Vamos criar um sourceCode fake que contém o snippet para testar o fluxo.
35
+ const fakeSourceCode = `
36
+ pragma solidity ^0.8.20;
37
+ contract CafeToken {
38
+ mapping(address => uint256) public balances;
39
+ event RewardRedeemed(address indexed user, uint256 amount, string recompensa);
40
+ function _burn(address account, uint256 amount) internal {
41
+ balances[account] -= amount;
42
+ }
43
+ function balanceOf(address account) public view returns (uint256) {
44
+ return balances[account];
45
+ }
46
+ function mint(address account, uint256 amount) public {
47
+ balances[account] += amount;
48
+ }
49
+ ${input.codeSnippet}
50
+ }
51
+ `;
52
+
53
+ const report = mapFindingToReport(input, fakeSourceCode);
54
+
55
+ console.log("Iniciando execução do Agente Tester com input.json...");
56
+ const result = await testerAgent.invoke({ report });
57
+
58
+ console.log("\n======= Resultado =======");
59
+ console.log("Status Final:", result.status);
60
+ console.log("Iterações:", result.iterations);
61
+ if (result.lastError) console.log("Último Erro:", result.lastError);
62
+
63
+ console.log("\n======= Código Gerado =======");
64
+ console.log(result.pocCode);
65
+ }
66
+
67
+ main().catch(console.error);