multi-agent-smart-contracts / AGENT_PROGRESS.md
Tales-Cunha
docs: create tester architecture definition
4e2e30e
|
Raw
History Blame Contribute Delete
15.9 kB

PoC Tester Agent β€” Progress Log

Repository: uandersonricardo/projeto-talp1 Branch: teste_agent Model used: google/gemini-3.1-flash-lite (via OpenRouter) Framework: LangGraph + Foundry Dataset: ASSERT-KTH/Proof-of-Patch β€” 22 real-world smart contract vulnerabilities with verified patches


Metric Definitions

Metric Formula Meaning
Reproducibility Rate PoCs passing on vulnerable version / 22 Agent generates a working exploit
Specificity Rate PoCs failing on patched version / reproducible PoCs Exploit is logically correct (patch stops it)
Overall Ground Truth reproducible AND specific / 22 Both conditions satisfied

Reference: The PoCo paper (Andersson et al., arXiv:2511.02780) evaluated on the same dataset using GPT-4o. Cases #003 and #015 have "inconclusive patches" per Β§5.3.1 β€” the patch fixes the bug but the PoC still passes because it tests side-effects unaffected by the fix. This is a known dataset limitation.


Benchmark History

Run 0 β€” Baseline (before any improvements)

Date: 2026-06-07 (pre-session) Commit: d849e68

Metric Value
Reproducibility 27.3% (6/22)
Specificity 0.0% (0/6)
Overall Ground Truth 0.0%
Avg Iterations 8.18

Reproducible: 001, 003, 008, 051, 054, 091 Main failure: ~63% were COMPILER_ERROR β€” agent guessing wrong import paths with no context.


Run 1 β€” Quick validation (5 cases, first improvements)

Date: 2026-06-08

Metric Value
Reproducibility 20.0% (1/5)
Specificity 0.0%
Avg Iterations 8.40

Run 2 β€” Quick validation (5 cases, BFS + minimal interface)

Date: 2026-06-08 Key changes: BFS test file selection, compileFailures counter, MINIMAL_INTERFACE escape hatch

Metric Value
Reproducibility 80.0% (4/5)
Specificity 0.0%
Avg Iterations 7.00

Notable: Case 015: 10 iters β†’ failed became 6 iters β†’ success thanks to MINIMAL_INTERFACE.


Run 3 β€” Full benchmark (22 cases)

Date: 2026-06-08 Commit: 1929f1b

Metric Value
Reproducibility 54.5% (12/22)
Specificity 0.0% (0/12)
Overall Ground Truth 0.0%
Avg Iterations 7.55

Reproducible (12): 001, 003, 008, 015, 032, 051, 054, 058, 066, 077, 091, 098 Failed (10): 009, 018, 020, 033, 039, 041, 042, 048, 049, 070 Error (1): 046 (forge not in PATH during setup)

Root cause of specificity=0%: Patch application was broken β€” cp -rv patches/ID/* copied a repo-name subdirectory INTO tempPatchDir instead of overwriting the actual source files. The "patched" version was still running the vulnerable code.


Per-Case Results (Run 3)

ID Vuln Type Project Repro Iter Failure Root Cause
001 multicall 2024-06-size βœ… 9 Patch not applied (dir bug)
003 access control 2023-07-pooltogether βœ… 5 Inconclusive patch (paper Β§5.3.1)
008 logic error 2023-09-centrifuge βœ… 2 Patch not applied (dir bug)
009 logic error 2023-10-caviar ❌ 10 lib/caviar/lib/oracle missing
015 access control 2023-07-pooltogether βœ… 5 Inconclusive patch (paper Β§5.3.1)
018 flash loan 2023-10-caviar ❌ 10 lib/caviar/lib/oracle missing
020 denial of service 2023-10-dopex ❌ 10 node_modules/@openzeppelin missing
032 access control 2022-06-putty βœ… 4 Patch not applied (dir bug)
033 logic error 2023-10-caviar ❌ 10 lib/caviar/lib/oracle missing
039 unchecked calls 2024-03-axis-finance ❌ 10 Compiled OK but logic reverted
041 reentrancy 2024-03-axis-finance ❌ 10 Compiled OK but logic reverted
042 access control 2023-10-cap ❌ 10 node_modules/@openzeppelin-upgradeable missing
046 n/a n/a ❌ β€” forge not in PATH (setup script error)
048 reentrancy 2023-10-caviar ❌ 10 lib/caviar/lib/oracle missing
049 access control 2024-01-salty ❌ 10 test/lib/UserFactory.sol missing
051 logic error 2023-11-panoptic βœ… 2 Patch not applied (dir bug)
054 logic error 2024-02-wise-lending βœ… 7 Patch not applied (dir bug)
058 logic error 2024-04-renzo βœ… 5 Patch not applied (dir bug)
066 unchecked calls 2024-05-munchables βœ… 8 Patch not applied (dir bug)
070 reentrancy 2024-08-ph ❌ 10 node_modules/@prb/test missing
077 reentrancy 2024-07-templegold βœ… 8 Patch not applied (dir bug)
091 logic error 2024-08-basin βœ… 9 Patch not applied (dir bug)
098 reentrancy 2022-05-cally βœ… 2 Patch not applied (dir bug)

Improvements Implemented

1. projectContextExtractor.ts (NEW)

Problem: LLM was guessing import paths β†’ 63% COMPILER_ERROR failures. Fix: Before generating PoC, reads remappings.txt, foundry.toml, and the most import-rich .t.sol test file (BFS across all subdirs, picks file with most import lines).

2. compileFailures Counter + MINIMAL_INTERFACE Escape Hatch

Problem: After 10 failed compile attempts, LLM stuck in loop on wrong imports. Fix: Counter increments per compile failure. After 3 consecutive β†’ switches to POC_MINIMAL_INTERFACE_PROMPT which forbids all external imports and uses inline interfaces.

3. Error Context in Fix Prompt

Problem: LLM only saw "Import path is WRONG" β€” not which file. Fix: analyzeFoundryLog extracts actual error lines (file path + line number) and surfaces them at the top of the fix prompt.

4. Patch Diff in Analysis Prompt

Problem: LLM generating generic assertions passing on both vulnerable and patched versions. Fix: Patch diff (unified diff of vulnerable vs patched contract) included in analyzeVulnerabilityNode with instruction: "assertion must PASS on vulnerable, FAIL on patched."

5. dependencyStubber.ts (NEW)

Problem: 7 cases fail because lib/caviar/lib/oracle/... or node_modules/@openzeppelin/... are absent from the sandbox. Fix: Runs forge build probe before generation, detects all "Source not found" errors, creates minimal stub contracts at those exact paths.

6. applyPatchSmart β€” Smart Patch Application (CRITICAL for specificity)

Problem: cp -rv patches/ID/* was copying a repo-name subdirectory INTO tempPatchDir. The "patched" test was running the vulnerable code. This is why specificity was always 0%. Fix: For each .sol in the patch dir, strips 1, 2, then 3 path prefix levels to find matching file in tempPatchDir and copies it correctly.

Patch file: patches/003/2023-07-pooltogether/vault/src/Vault.sol
tempPatchDir: copy of findings/003/2023-07-pooltogether/vault/

strip=1: vault/src/Vault.sol β†’ NOT in tempPatchDir
strip=2: src/Vault.sol β†’ EXISTS βœ… β†’ copy applied

7. computePatchDiff β€” Correct Diff Calculation

Problem: Previous diff command tried a hardcoded path that didn't match the nested structure. Fix: Uses same strip-depth logic as applyPatchSmart to find the right file pair.

8. Improved Prompts

  • ANALYZE_VULNERABILITY_PROMPT: asks for specific assertion that fails after patching
  • POC_COMPILE_FIX_PROMPT: import resolution hierarchy (remappings β†’ existing test β†’ inline)
  • POC_TEST_FIX_PROMPT: added reentrancy receive() callback pattern + unchecked return pattern
  • POC_MINIMAL_INTERFACE_PROMPT (NEW): full template for zero-external-import strategy

Agent Architecture

VulnerabilityReport
       β”‚
       β–Ό
  oracleNode
  β”œβ”€β”€ generateLocalScaffold()
  β”œβ”€β”€ analyzeSolidityFile()           ← contract API extraction
  β”œβ”€β”€ extractProjectContext()          ← remappings + best .t.sol (BFS)
  └── createMissingDependencyStubs()   ← stubs missing lib/node_modules
       β”‚
       β–Ό
  analyzeVulnerabilityNode            ← LLM: root cause + specific assertion
  [ANALYZE_VULNERABILITY_PROMPT + patch diff]
       β”‚
       β–Ό
  generatePoCNode ←──────────────────────────┐
  β”œβ”€β”€ INITIAL: [POC_INITIAL_PROMPT]           β”‚
  β”œβ”€β”€ FIX_COMPILE (failures < 3):             β”‚
  β”‚   [POC_COMPILE_FIX_PROMPT]               β”‚
  β”œβ”€β”€ MINIMAL_INTERFACE (failures >= 3):      β”‚
  β”‚   [POC_MINIMAL_INTERFACE_PROMPT]          β”‚
  └── FIX_LOGIC: [POC_TEST_FIX_PROMPT]       β”‚
       β”‚                                      β”‚
       β–Ό                                      β”‚
  runFoundryNode β†’ analyzeFoundryLog          β”‚
       β”‚                                      β”‚
       β”œβ”€β”€ success ──────────────────── END   β”‚
       β”‚                                      β”‚
       └── failed + iters < 10 β†’ reflectNode β”€β”˜

Next Steps

P0 β€” Run benchmark with patch fix + dep stubs (Run 4)

Expected: Reproducibility ~68%, Specificity ~30%, Ground Truth ~20%

P1 β€” Add refineSpecificityNode

If PoC passes on both versions, run a refinement step:

  • Show: current PoC + patch diff
  • Ask: "Make the assertion target exactly what the patch changes"

P2 β€” Docker update

The Dockerfile builds TypeScript, so new files are included automatically. Fix needed: setup-sandbox.sh may not find forge in Docker because foundryup sets PATH in ~/.bashrc (not sourced in non-interactive shells). Fix: add source ~/.foundry/env or export PATH="$HOME/.foundry/bin:$PATH" explicitly.

P3 β€” Integration check

The tester agent's public API (VulnerabilityReport β†’ { status, solidityCode }) is unchanged. The server.ts route calling runPoCGenerator() still works. Docker image needs rebuild after code changes.


Expected Run 4 Results

Metric Run 3 Target Run 4
Reproducibility 54.5% ~68%
Specificity 0.0% ~30%
Overall Ground Truth 0.0% ~20%
Avg Iterations 7.55 <7.0

Production vs Benchmark Gap Analysis

What the production pipeline actually sends to the tester

User request
    β”‚
    β–Ό Coder Agent
coderResult.contract  ← a single Solidity string (the generated contract)
    β”‚
    β–Ό Auditor Agent (receives repoPath with Contract.sol + README.md)
auditorResult.findings[0]  ← ONE finding
    β”‚
    β–Ό mapFindingToReport()  ← the ONLY bridge between auditor and tester

mapFindingToReport currently passes to the tester:

Field Source Used by tester?
id derived from title βœ… identifier only
severity finding.severity label only
type finding.type βœ… in analysis prompt
title finding.title βœ… in analysis prompt
description finding.description βœ… in analysis prompt
affectedContract.sourceCode coderResult.contract βœ… shown to LLM
affectedContract.name extracted from path βœ…
attackVector exploitablePaths[0] βœ… in analysis prompt
exploitablePaths judgeReview.exploitablePaths βœ… passed
codeSnippet finding.codeSnippet βœ… passed
location finding.location βœ… passed

What the auditor produces but the tester NEVER receives:

Auditor field Value Why it would help the tester
finding.recommendation Human-readable fix suggestion Tells tester what the PATCH would look like β†’ key for specific assertions
finding.judgeReview.review Judge's analysis of exploitability More precise attack reasoning than just description
finding.judgeReview.confidence 0-100 confidence score Tester could skip low-confidence findings
auditorResult.repoContext Full structured protocol context Gives tester knowledge of cross-contract interactions
auditorResult.fileTree Directory tree of the repo Helps tester find the right imports
All other .sol files Source of ALL contracts Tester only gets ONE contract; misses dependencies

Missing in production but present in benchmark:

Field Benchmark Production
customSandboxDir βœ… real project folder ❌ NOT SET (uses generic /tmp/poc-sandbox)
referenceTestCode βœ… real test files ❌ NOT SET
patchDiff βœ… computed from dataset ❌ N/A (no patch in production)

The critical gap: In production, customSandboxDir is null/undefined, so:

  • BFS test file selection β†’ SKIPPED
  • projectContextExtractor β†’ SKIPPED
  • dependencyStubber β†’ SKIPPED
  • Test file cleanup β†’ SKIPPED
  • The tester runs in the generic /tmp/poc-sandbox with NO project context

All the improvements that boosted benchmark from 27% β†’ 54% are benchmark-only.


Model vs Agent Quality Plateau

How to tell them apart:

Symptom Model limit Agent limit
Correct assertion but setup wrong βœ… Agent can fix
Wrong assertion (generic, too broad) βœ… Model limit Could improve with better prompting
Compile errors even with MINIMAL_INTERFACE βœ… Agent can fix
Passes vulnerable but also passes patched βœ… Model semantics Partially agent (patch context)
Correct overall but random failures across runs βœ… Temperature/stochastic

Current evidence points:

  • gemini-3.1-flash-lite is a very small/cheap model β†’ likely hitting model ceiling for complex semantic reasoning
  • The compile errors are 100% agent-fixable (and we mostly did)
  • Specificity failures: partly agent bug (patch not applied) + partly model (generic assertions)

Test: try 3 cases with a stronger model to see the ceiling:

OPENROUTER_MODEL="google/gemini-2.0-flash" BENCHMARK_LIMIT=3 npx tsx src/benchmark/runTesterBenchmark.ts

If specificity jumps to >50% with the stronger model, it's the model. If not, it's the agent prompting.


Next Actions (Priority Order)

1. Fix production gap β€” mapFindingToReport (HIGH VALUE, LOW EFFORT)

Add the missing rich context from the auditor to what the tester receives:

export function mapFindingToReport(finding: any, sourceCode: string, 
                                    repoContext?: string): VulnerabilityReport {
  return {
    // ... existing fields
    description: [
      finding.description,
      finding.recommendation ? `\nFix recommendation: ${finding.recommendation}` : "",
      finding.judgeReview?.review ? `\nJudge analysis: ${finding.judgeReview.review}` : "",
      repoContext ? `\nProtocol context: ${repoContext.slice(0, 1000)}` : "",
    ].filter(Boolean).join("\n"),
    // The recommendation tells the tester what should NOT work after the fix
    // which is the key for writing a specific assertion
  };
}

2. Pass repoPath as customSandboxDir in production

In server.ts, pass outputDir (where Contract.sol was written) as customSandboxDir:

const report = mapFindingToReport(auditorResult.findings[0], coderResult.contract, 
                                   auditorResult.repoContext);
report.customSandboxDir = outputDir;  // ← enables all context improvements

Since production uses a single Contract.sol with no remappings or test files, the context extractor will find no remappings (graceful fallback), and the dep stubber will run a probe but find nothing missing (also fine).

3. Lower MAX_ITERATIONS from 10 to 6

Credits are limited. 10 iterations is too many for flash-lite which repeats itself after ~5.

4. Run full benchmark to measure Run 4

After the test file cleanup fix + patch fix are confirmed working.