tanaymitra98 commited on
Commit
7998b79
·
1 Parent(s): 6e43eb6

Rebuild task setup with 4 enabled graded tasks and multi-task inference

Browse files
Files changed (6) hide show
  1. README.md +4 -2
  2. data/manifest.json +55 -1
  3. graders.py +48 -0
  4. inference.py +93 -45
  5. openenv.yaml +23 -6
  6. server/app.py +142 -86
README.md CHANGED
@@ -15,10 +15,11 @@ SolidityGuard is an OpenEnv RL environment that trains agents to review Solidity
15
 
16
  ## Overview
17
 
18
- SolidityGuard provides a comprehensive auditing platform for Solidity smart contracts with three difficulty levels:
19
  - **Task 1 (Easy)**: Best Practices & Syntax Issues
20
  - **Task 2 (Medium)**: Gas Optimization Opportunities
21
  - **Task 3 (Hard)**: Security Vulnerabilities
 
22
 
23
  ## Quick Start
24
 
@@ -68,6 +69,7 @@ JSON array of findings:
68
  - Task 1: Best practices and syntax (missing SPDX, old compiler, missing NatSpec, deprecated constructor)
69
  - Task 2: Gas optimization (unbounded loops, redundant storage reads, custom errors)
70
  - Task 3: Security vulnerabilities (reentrancy, missing access control, tx.origin auth)
 
71
 
72
  ## API Endpoints
73
 
@@ -102,4 +104,4 @@ Health check endpoint.
102
  ## Notes
103
  - Runtime should stay under 20 minutes on 2 vCPU / 8 GB.
104
  - Docker build must succeed for submission.
105
- - Use Hugging Face Inference Providers for LLM inference.
 
15
 
16
  ## Overview
17
 
18
+ SolidityGuard provides a comprehensive auditing platform for Solidity smart contracts with four tasks:
19
  - **Task 1 (Easy)**: Best Practices & Syntax Issues
20
  - **Task 2 (Medium)**: Gas Optimization Opportunities
21
  - **Task 3 (Hard)**: Security Vulnerabilities
22
+ - **Task 4 (Hard)**: Comprehensive Audit (cross-category)
23
 
24
  ## Quick Start
25
 
 
69
  - Task 1: Best practices and syntax (missing SPDX, old compiler, missing NatSpec, deprecated constructor)
70
  - Task 2: Gas optimization (unbounded loops, redundant storage reads, custom errors)
71
  - Task 3: Security vulnerabilities (reentrancy, missing access control, tx.origin auth)
72
+ - Task 4: Comprehensive audit across best practices, gas, and security
73
 
74
  ## API Endpoints
75
 
 
104
  ## Notes
105
  - Runtime should stay under 20 minutes on 2 vCPU / 8 GB.
106
  - Docker build must succeed for submission.
107
+ - Use Hugging Face Inference Providers for LLM inference.
data/manifest.json CHANGED
@@ -412,5 +412,59 @@
412
  "severity": "Medium"
413
  }
414
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
- ]
 
412
  "severity": "Medium"
413
  }
414
  ]
415
+ },
416
+ {
417
+ "id": "t4_sample_1",
418
+ "task_id": "task_4_comprehensive_audit",
419
+ "source_path": "data/samples/task1/old_pragma.sol",
420
+ "metadata": {
421
+ "contract_name": "OldPragma",
422
+ "compiler_version": "0.4.25",
423
+ "file_path": "old_pragma.sol"
424
+ },
425
+ "labels": [
426
+ {
427
+ "issue_type": "old_compiler_version",
428
+ "line_number": 2,
429
+ "description": "Compiler version below 0.8.x",
430
+ "severity": "Low"
431
+ }
432
+ ]
433
+ },
434
+ {
435
+ "id": "t4_sample_2",
436
+ "task_id": "task_4_comprehensive_audit",
437
+ "source_path": "data/samples/task2/inefficient_loop.sol",
438
+ "metadata": {
439
+ "contract_name": "LoopGas",
440
+ "compiler_version": "0.8.17",
441
+ "file_path": "inefficient_loop.sol"
442
+ },
443
+ "labels": [
444
+ {
445
+ "issue_type": "unbounded_loop",
446
+ "line_number": 10,
447
+ "description": "Loop uses dynamic array length without bounds",
448
+ "severity": "Medium"
449
+ }
450
+ ]
451
+ },
452
+ {
453
+ "id": "t4_sample_3",
454
+ "task_id": "task_4_comprehensive_audit",
455
+ "source_path": "data/samples/task3/tx_origin_auth.sol",
456
+ "metadata": {
457
+ "contract_name": "OriginAuth",
458
+ "compiler_version": "0.8.20",
459
+ "file_path": "tx_origin_auth.sol"
460
+ },
461
+ "labels": [
462
+ {
463
+ "issue_type": "tx_origin_auth",
464
+ "line_number": 11,
465
+ "description": "Authorization uses tx.origin",
466
+ "severity": "Critical"
467
+ }
468
+ ]
469
  }
470
+ ]
graders.py CHANGED
@@ -24,6 +24,30 @@ class Grader:
24
  """Grade the action against expected results. Returns (score, details)."""
25
  return grade_action(action, expected)
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  def _normalize_issue(issue: Dict[str, Any]) -> Issue:
29
  return Issue(
@@ -202,3 +226,27 @@ def grade(
202
  ) -> Tuple[float, Dict[str, Any]]:
203
  """Compatibility alias for validators expecting graders:grade."""
204
  return grade_action(action, expected)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  """Grade the action against expected results. Returns (score, details)."""
25
  return grade_action(action, expected)
26
 
27
+ @staticmethod
28
+ def grade_task_1(
29
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
30
+ ) -> Tuple[float, Dict[str, Any]]:
31
+ return grade_action(action, expected)
32
+
33
+ @staticmethod
34
+ def grade_task_2(
35
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
36
+ ) -> Tuple[float, Dict[str, Any]]:
37
+ return grade_action(action, expected)
38
+
39
+ @staticmethod
40
+ def grade_task_3(
41
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
42
+ ) -> Tuple[float, Dict[str, Any]]:
43
+ return grade_action(action, expected)
44
+
45
+ @staticmethod
46
+ def grade_task_4(
47
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
48
+ ) -> Tuple[float, Dict[str, Any]]:
49
+ return grade_action(action, expected)
50
+
51
 
52
  def _normalize_issue(issue: Dict[str, Any]) -> Issue:
53
  return Issue(
 
226
  ) -> Tuple[float, Dict[str, Any]]:
227
  """Compatibility alias for validators expecting graders:grade."""
228
  return grade_action(action, expected)
229
+
230
+
231
+ def grade_task_1(
232
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
233
+ ) -> Tuple[float, Dict[str, Any]]:
234
+ return grade_action(action, expected)
235
+
236
+
237
+ def grade_task_2(
238
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
239
+ ) -> Tuple[float, Dict[str, Any]]:
240
+ return grade_action(action, expected)
241
+
242
+
243
+ def grade_task_3(
244
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
245
+ ) -> Tuple[float, Dict[str, Any]]:
246
+ return grade_action(action, expected)
247
+
248
+
249
+ def grade_task_4(
250
+ action: List[Dict[str, Any]], expected: List[Dict[str, Any]]
251
+ ) -> Tuple[float, Dict[str, Any]]:
252
+ return grade_action(action, expected)
inference.py CHANGED
@@ -30,9 +30,15 @@ API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY")
30
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
31
  MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-72B-Instruct"
32
 
33
- # Task configuration - validator sets this for each task run
34
- TASK_NAME = os.getenv("SOLIDITYGUARD_TASK") or os.getenv("TASK_NAME") or "task_1_best_practices"
35
  BENCHMARK = os.getenv("SOLIDITYGUARD_BENCHMARK", "solidityguard")
 
 
 
 
 
 
36
 
37
  MAX_STEPS = 1 # Each task has 1 step in our environment
38
  SUCCESS_SCORE_THRESHOLD = 0.1
@@ -46,7 +52,9 @@ def log_start(task: str, env: str, model: str) -> None:
46
  print(f"[{START_TAG}] task={task} env={env} model={model}", flush=True)
47
 
48
 
49
- def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str]) -> None:
 
 
50
  error_val = error if error else "null"
51
  done_val = str(done).lower()
52
  print(
@@ -84,6 +92,7 @@ def _build_prompt(source_code: str, task_id: str) -> str:
84
  "task_1_best_practices": "Find syntax and best-practice issues: missing SPDX license, old compiler version (<0.8.x), missing NatSpec comments, deprecated constructor syntax.",
85
  "task_2_gas_optimization": "Find gas optimization opportunities: unbounded loops, redundant storage reads, missing custom errors (use custom errors instead of require strings).",
86
  "task_3_security": "Find security vulnerabilities: reentrancy bugs, missing access control, tx.origin usage for authorization, integer overflow/underflow.",
 
87
  }
88
  return (
89
  "Review this Solidity contract and return ONLY a JSON array of findings. "
@@ -96,7 +105,7 @@ def _build_prompt(source_code: str, task_id: str) -> str:
96
  def _fallback_actions(source_code: str, task_id: str) -> List[Dict[str, Any]]:
97
  """Deterministic fallback when LLM call fails or returns empty."""
98
  lowered = source_code.lower()
99
-
100
  if task_id == "task_1_best_practices":
101
  return [
102
  {
@@ -169,6 +178,46 @@ def _fallback_actions(source_code: str, task_id: str) -> List[Dict[str, Any]]:
169
  }
170
  ]
171
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  # Default fallback
173
  return [
174
  {
@@ -197,13 +246,13 @@ def _call_model(client: OpenAI, prompt: str) -> List[Dict[str, Any]]:
197
  stream=False,
198
  )
199
  content = (response.choices[0].message.content or "[]").strip()
200
-
201
  # Handle markdown code blocks
202
  if content.startswith("```"):
203
  parts = content.split("```")
204
  if len(parts) >= 2:
205
  content = parts[1].replace("json", "", 1).strip()
206
-
207
  parsed = json.loads(content)
208
  if isinstance(parsed, list):
209
  return parsed
@@ -220,48 +269,47 @@ def main() -> None:
220
  steps_taken = 0
221
  score = 0.0
222
  success = False
 
223
 
224
- log_start(task=TASK_NAME, env=BENCHMARK, model=MODEL_NAME)
225
 
226
  try:
227
- # Reset environment for this specific task
228
- observation = env.reset(task_id=TASK_NAME)
229
- last_reward = 0.0
230
-
231
- for step in range(1, MAX_STEPS + 1):
232
- error: Optional[str] = None
233
- action_text = "[]"
234
-
235
- try:
236
- # Build prompt and call model
237
- prompt = _build_prompt(observation["source_code"], TASK_NAME)
238
- actions = _call_model(client, prompt)
239
-
240
- # Use fallback if model returns empty
241
- if not actions:
242
- actions = _fallback_actions(observation["source_code"], TASK_NAME)
243
-
244
- # Execute step
245
- result = env.step(actions)
246
-
247
- reward = _safe_score(float(result.get("reward", 0.0)))
248
- done = result.get("done", True)
249
-
250
- action_text = json.dumps(actions, ensure_ascii=True, separators=(",", ":"))
251
-
252
- except Exception as exc:
253
- error = str(exc)
254
- reward = 0.01
255
- done = True
256
-
257
- rewards.append(reward)
258
- steps_taken = step
259
- last_reward = reward
260
-
261
- log_step(step=step, action=action_text, reward=reward, done=done, error=error)
262
-
263
- if done:
264
- break
265
 
266
  # Calculate final score
267
  score = _safe_score(sum(rewards) / max(len(rewards), 1))
 
30
  API_BASE_URL = os.getenv("API_BASE_URL") or "https://router.huggingface.co/v1"
31
  MODEL_NAME = os.getenv("MODEL_NAME") or "Qwen/Qwen2.5-72B-Instruct"
32
 
33
+ # Task configuration - validator may set this for single-task runs
34
+ TASK_NAME = os.getenv("SOLIDITYGUARD_TASK") or os.getenv("TASK_NAME")
35
  BENCHMARK = os.getenv("SOLIDITYGUARD_BENCHMARK", "solidityguard")
36
+ DEFAULT_TASKS = [
37
+ "task_1_best_practices",
38
+ "task_2_gas_optimization",
39
+ "task_3_security",
40
+ "task_4_comprehensive_audit",
41
+ ]
42
 
43
  MAX_STEPS = 1 # Each task has 1 step in our environment
44
  SUCCESS_SCORE_THRESHOLD = 0.1
 
52
  print(f"[{START_TAG}] task={task} env={env} model={model}", flush=True)
53
 
54
 
55
+ def log_step(
56
+ step: int, action: str, reward: float, done: bool, error: Optional[str]
57
+ ) -> None:
58
  error_val = error if error else "null"
59
  done_val = str(done).lower()
60
  print(
 
92
  "task_1_best_practices": "Find syntax and best-practice issues: missing SPDX license, old compiler version (<0.8.x), missing NatSpec comments, deprecated constructor syntax.",
93
  "task_2_gas_optimization": "Find gas optimization opportunities: unbounded loops, redundant storage reads, missing custom errors (use custom errors instead of require strings).",
94
  "task_3_security": "Find security vulnerabilities: reentrancy bugs, missing access control, tx.origin usage for authorization, integer overflow/underflow.",
95
+ "task_4_comprehensive_audit": "Find a complete mix of issues across best-practices, gas optimization, and security vulnerabilities.",
96
  }
97
  return (
98
  "Review this Solidity contract and return ONLY a JSON array of findings. "
 
105
  def _fallback_actions(source_code: str, task_id: str) -> List[Dict[str, Any]]:
106
  """Deterministic fallback when LLM call fails or returns empty."""
107
  lowered = source_code.lower()
108
+
109
  if task_id == "task_1_best_practices":
110
  return [
111
  {
 
178
  }
179
  ]
180
 
181
+ if task_id == "task_4_comprehensive_audit":
182
+ findings: List[Dict[str, Any]] = []
183
+ if "pragma solidity" in lowered:
184
+ findings.append(
185
+ {
186
+ "issue_type": "old_compiler_version",
187
+ "line_number": _find_line_number(source_code, "pragma solidity", 2),
188
+ "description": "Compiler pragma should use ^0.8.x",
189
+ "severity": "Low",
190
+ }
191
+ )
192
+ if "for" in lowered and ".length" in lowered:
193
+ findings.append(
194
+ {
195
+ "issue_type": "unbounded_loop",
196
+ "line_number": _find_line_number(source_code, "for", 10),
197
+ "description": "Loop uses dynamic array length without bounds",
198
+ "severity": "Medium",
199
+ }
200
+ )
201
+ if "tx.origin" in lowered:
202
+ findings.append(
203
+ {
204
+ "issue_type": "tx_origin_auth",
205
+ "line_number": _find_line_number(source_code, "tx.origin", 11),
206
+ "description": "Authorization uses tx.origin",
207
+ "severity": "Critical",
208
+ }
209
+ )
210
+ if not findings:
211
+ findings = [
212
+ {
213
+ "issue_type": "missing_spdx",
214
+ "line_number": 1,
215
+ "description": "Missing SPDX license identifier",
216
+ "severity": "Low",
217
+ }
218
+ ]
219
+ return findings
220
+
221
  # Default fallback
222
  return [
223
  {
 
246
  stream=False,
247
  )
248
  content = (response.choices[0].message.content or "[]").strip()
249
+
250
  # Handle markdown code blocks
251
  if content.startswith("```"):
252
  parts = content.split("```")
253
  if len(parts) >= 2:
254
  content = parts[1].replace("json", "", 1).strip()
255
+
256
  parsed = json.loads(content)
257
  if isinstance(parsed, list):
258
  return parsed
 
269
  steps_taken = 0
270
  score = 0.0
271
  success = False
272
+ task_list = [TASK_NAME] if TASK_NAME else DEFAULT_TASKS
273
 
274
+ log_start(task=",".join(task_list), env=BENCHMARK, model=MODEL_NAME)
275
 
276
  try:
277
+ for task_id in task_list:
278
+ observation = env.reset(task_id=task_id)
279
+
280
+ for _ in range(MAX_STEPS):
281
+ steps_taken += 1
282
+ error: Optional[str] = None
283
+ action_text = "[]"
284
+
285
+ try:
286
+ prompt = _build_prompt(observation["source_code"], task_id)
287
+ actions = _call_model(client, prompt)
288
+ if not actions:
289
+ actions = _fallback_actions(observation["source_code"], task_id)
290
+
291
+ result = env.step(actions)
292
+ reward = _safe_score(float(result.get("reward", 0.0)))
293
+ done = result.get("done", True)
294
+ action_text = json.dumps(
295
+ actions, ensure_ascii=True, separators=(",", ":")
296
+ )
297
+ except Exception as exc:
298
+ error = str(exc)
299
+ reward = 0.01
300
+ done = True
301
+
302
+ rewards.append(reward)
303
+ log_step(
304
+ step=steps_taken,
305
+ action=action_text,
306
+ reward=reward,
307
+ done=done,
308
+ error=error,
309
+ )
310
+
311
+ if done:
312
+ break
 
 
313
 
314
  # Calculate final score
315
  score = _safe_score(sum(rewards) / max(len(rewards), 1))
openenv.yaml CHANGED
@@ -9,27 +9,44 @@ tasks:
9
  enabled: true
10
  description: Detect syntax and best-practice issues in Solidity contracts.
11
  max_steps: 1
12
- grader: "graders:grade_action"
13
  graders:
14
- - "graders:grade_action"
 
 
15
  - id: task_2_gas_optimization
16
  name: Gas Optimization Audit
17
  difficulty: medium
18
  enabled: true
19
  description: Detect gas optimization opportunities in Solidity contracts.
20
  max_steps: 1
21
- grader: "graders:grade_action"
22
  graders:
23
- - "graders:grade_action"
 
 
24
  - id: task_3_security
25
  name: Security Vulnerability Audit
26
  difficulty: hard
27
  enabled: true
28
  description: Detect security vulnerabilities in Solidity contracts.
29
  max_steps: 1
30
- grader: "graders:grade_action"
31
  graders:
32
- - "graders:grade_action"
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  schemas:
34
  observation:
35
  type: object
 
9
  enabled: true
10
  description: Detect syntax and best-practice issues in Solidity contracts.
11
  max_steps: 1
12
+ grader: "graders:grade_task_1"
13
  graders:
14
+ - name: task_1_grader
15
+ enabled: true
16
+ entrypoint: "graders:grade_task_1"
17
  - id: task_2_gas_optimization
18
  name: Gas Optimization Audit
19
  difficulty: medium
20
  enabled: true
21
  description: Detect gas optimization opportunities in Solidity contracts.
22
  max_steps: 1
23
+ grader: "graders:grade_task_2"
24
  graders:
25
+ - name: task_2_grader
26
+ enabled: true
27
+ entrypoint: "graders:grade_task_2"
28
  - id: task_3_security
29
  name: Security Vulnerability Audit
30
  difficulty: hard
31
  enabled: true
32
  description: Detect security vulnerabilities in Solidity contracts.
33
  max_steps: 1
34
+ grader: "graders:grade_task_3"
35
  graders:
36
+ - name: task_3_grader
37
+ enabled: true
38
+ entrypoint: "graders:grade_task_3"
39
+ - id: task_4_comprehensive_audit
40
+ name: Comprehensive Audit
41
+ difficulty: hard
42
+ enabled: true
43
+ description: Perform a complete audit covering best practices, gas, and security.
44
+ max_steps: 1
45
+ grader: "graders:grade_task_4"
46
+ graders:
47
+ - name: task_4_grader
48
+ enabled: true
49
+ entrypoint: "graders:grade_task_4"
50
  schemas:
51
  observation:
52
  type: object
server/app.py CHANGED
@@ -464,6 +464,7 @@ def root() -> str:
464
  </html>
465
  """
466
 
 
467
  @app.get("/health")
468
  def health() -> Dict[str, str]:
469
  return {"status": "ok"}
@@ -500,14 +501,14 @@ def generate_report(request: ReportRequest) -> Dict[str, Any]:
500
  try:
501
  # Reset to get the contract
502
  observation = env.reset(task_id=request.task_id)
503
-
504
  # Get contract metadata
505
  metadata = observation["metadata"]
506
  source_code = observation["source_code"]
507
-
508
  # Calculate risk metrics
509
  risk_metrics = _calculate_risk_metrics(source_code, request.task_id)
510
-
511
  # Generate summary
512
  report = {
513
  "contract_info": {
@@ -515,27 +516,31 @@ def generate_report(request: ReportRequest) -> Dict[str, Any]:
515
  "compiler_version": metadata["compiler_version"],
516
  "file_path": metadata["file_path"],
517
  "task_category": request.task_id,
518
- "lines_of_code": len(source_code.split('\n'))
519
  },
520
  "risk_assessment": risk_metrics,
521
  "recommendations": _generate_recommendations(risk_metrics),
522
  "timestamp": "2026-04-06T12:00:00Z", # Mock timestamp
523
- "report_version": "2.0.0"
524
  }
525
-
526
  if request.include_fixes:
527
- report["suggested_fixes"] = _get_fix_suggestions(source_code, request.task_id)
528
-
 
 
529
  if request.include_exploits:
530
- report["exploit_scenarios"] = _get_exploit_scenarios(source_code, request.task_id)
531
-
 
 
532
  return report
533
-
534
  except Exception as exc:
535
  raise HTTPException(status_code=400, detail=str(exc))
536
 
537
 
538
- @app.get("/dashboard")
539
  def get_dashboard() -> Dict[str, Any]:
540
  """Get dashboard overview of all contract categories."""
541
  try:
@@ -544,159 +549,210 @@ def get_dashboard() -> Dict[str, Any]:
544
  "total_samples": 18,
545
  "categories": 3,
546
  "avg_risk_score": 0.65,
547
- "last_updated": "2026-04-06T12:00:00Z"
548
  },
549
  "category_breakdown": {
550
  "task_1_best_practices": {
551
  "sample_count": 6,
552
  "avg_severity": "Low",
553
- "common_issues": ["missing_spdx", "old_compiler_version", "missing_natspec"]
 
 
 
 
554
  },
555
  "task_2_gas_optimization": {
556
- "sample_count": 6,
557
  "avg_severity": "Medium",
558
- "common_issues": ["unbounded_loop", "redundant_storage_read", "poor_struct_packing"]
 
 
 
 
559
  },
560
  "task_3_security": {
561
  "sample_count": 6,
562
- "avg_severity": "Critical",
563
- "common_issues": ["reentrancy", "missing_access_control", "tx_origin_auth"]
564
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  },
566
  "agent_stats": {
567
  "multi_agent_enabled": True,
568
  "analyzer_accuracy": 0.85,
569
  "verifier_precision": 0.90,
570
- "risk_scorer_coverage": 0.95
571
- }
572
  }
573
-
574
  return dashboard_data
575
-
576
  except Exception as exc:
577
  raise HTTPException(status_code=400, detail=str(exc))
578
 
579
 
580
  def _calculate_risk_metrics(source_code: str, task_id: str) -> Dict[str, Any]:
581
  """Calculate comprehensive risk metrics for a contract."""
582
-
583
  # Basic metrics
584
- lines_of_code = len(source_code.split('\n'))
585
- cyclomatic_complexity = source_code.count('if ') + source_code.count('for ') + source_code.count('while ')
586
-
 
 
 
 
587
  # Security indicators
588
- has_external_calls = 'call{' in source_code or '.call(' in source_code
589
- has_state_variables = 'mapping(' in source_code or 'uint' in source_code
590
- has_payable = 'payable' in source_code
591
-
592
  # Calculate overall risk score
593
  base_risk = 0.3
594
  if task_id == "task_3_security":
595
  base_risk = 0.8
596
- elif task_id == "task_2_gas_optimization":
 
 
597
  base_risk = 0.5
598
-
599
  complexity_factor = min(cyclomatic_complexity * 0.1, 0.3)
600
  external_call_factor = 0.2 if has_external_calls else 0.0
601
  payable_factor = 0.1 if has_payable else 0.0
602
-
603
- overall_risk = min(base_risk + complexity_factor + external_call_factor + payable_factor, 1.0)
604
-
 
 
605
  return {
606
  "overall_risk_score": round(overall_risk, 3),
607
- "risk_category": "High" if overall_risk >= 0.7 else "Medium" if overall_risk >= 0.4 else "Low",
 
 
 
 
608
  "complexity_score": cyclomatic_complexity,
609
  "lines_of_code": lines_of_code,
610
  "has_external_calls": has_external_calls,
611
  "has_state_variables": has_state_variables,
612
  "has_payable_functions": has_payable,
613
- "recommended_review_time": f"{max(15, lines_of_code * 2)} minutes"
614
  }
615
 
616
 
617
  def _generate_recommendations(risk_metrics: Dict[str, Any]) -> List[str]:
618
  """Generate audit recommendations based on risk metrics."""
619
-
620
  recommendations = []
621
-
622
  if risk_metrics["overall_risk_score"] >= 0.7:
623
  recommendations.append("🔴 HIGH RISK: Requires immediate security review")
624
  recommendations.append("Consider formal verification for critical functions")
625
-
626
  if risk_metrics["has_external_calls"]:
627
- recommendations.append("⚠️ External calls detected: Review for reentrancy vulnerabilities")
628
-
 
 
629
  if risk_metrics["has_payable_functions"]:
630
- recommendations.append("💰 Payable functions detected: Ensure proper access controls")
631
-
 
 
632
  if risk_metrics["complexity_score"] > 10:
633
- recommendations.append("🧩 High complexity: Consider breaking into smaller functions")
634
-
 
 
635
  if risk_metrics["lines_of_code"] > 100:
636
  recommendations.append("📏 Large contract: Consider modularization")
637
-
638
  recommendations.append("✅ Run static analysis tools (Slither, Mythril)")
639
  recommendations.append("🧪 Implement comprehensive test coverage")
640
-
641
  return recommendations
642
 
643
 
644
  def _get_fix_suggestions(source_code: str, task_id: str) -> List[Dict[str, str]]:
645
  """Get specific fix suggestions for common issues."""
646
-
647
  suggestions = []
648
-
649
  if not source_code.strip().startswith("// SPDX"):
650
- suggestions.append({
651
- "issue": "Missing SPDX License",
652
- "fix": "Add '// SPDX-License-Identifier: MIT' at the top of the file",
653
- "priority": "Low"
654
- })
655
-
 
 
656
  if "0.4." in source_code or "0.7." in source_code:
657
- suggestions.append({
658
- "issue": "Outdated Solidity Version",
659
- "fix": "Update to 'pragma solidity ^0.8.0;' for better security",
660
- "priority": "Medium"
661
- })
662
-
 
 
663
  if "tx.origin" in source_code:
664
- suggestions.append({
665
- "issue": "tx.origin Usage",
666
- "fix": "Replace 'tx.origin' with 'msg.sender' for proper authentication",
667
- "priority": "High"
668
- })
669
-
 
 
670
  return suggestions
671
 
672
 
673
  def _get_exploit_scenarios(source_code: str, task_id: str) -> List[Dict[str, str]]:
674
  """Get potential exploit scenarios for security issues."""
675
-
676
  scenarios = []
677
-
678
  if "call{" in source_code and "balances[" in source_code:
679
- scenarios.append({
680
- "vulnerability": "Reentrancy Attack",
681
- "scenario": "Attacker creates malicious contract with fallback function that calls withdraw() recursively",
682
- "impact": "Complete drainage of contract funds",
683
- "mitigation": "Implement checks-effects-interactions pattern or ReentrancyGuard"
684
- })
685
-
 
 
686
  if "tx.origin" in source_code:
687
- scenarios.append({
688
- "vulnerability": "tx.origin Phishing",
689
- "scenario": "Attacker tricks user into calling malicious contract that forwards transactions",
690
- "impact": "Unauthorized access to protected functions",
691
- "mitigation": "Use msg.sender instead of tx.origin for authentication"
692
- })
693
-
 
 
694
  return scenarios
695
 
 
696
  def main():
697
  import uvicorn
 
698
  uvicorn.run("server.app:app", host="0.0.0.0", port=7860)
699
 
 
700
  if __name__ == "__main__":
701
  main()
702
-
 
464
  </html>
465
  """
466
 
467
+
468
  @app.get("/health")
469
  def health() -> Dict[str, str]:
470
  return {"status": "ok"}
 
501
  try:
502
  # Reset to get the contract
503
  observation = env.reset(task_id=request.task_id)
504
+
505
  # Get contract metadata
506
  metadata = observation["metadata"]
507
  source_code = observation["source_code"]
508
+
509
  # Calculate risk metrics
510
  risk_metrics = _calculate_risk_metrics(source_code, request.task_id)
511
+
512
  # Generate summary
513
  report = {
514
  "contract_info": {
 
516
  "compiler_version": metadata["compiler_version"],
517
  "file_path": metadata["file_path"],
518
  "task_category": request.task_id,
519
+ "lines_of_code": len(source_code.split("\n")),
520
  },
521
  "risk_assessment": risk_metrics,
522
  "recommendations": _generate_recommendations(risk_metrics),
523
  "timestamp": "2026-04-06T12:00:00Z", # Mock timestamp
524
+ "report_version": "2.0.0",
525
  }
526
+
527
  if request.include_fixes:
528
+ report["suggested_fixes"] = _get_fix_suggestions(
529
+ source_code, request.task_id
530
+ )
531
+
532
  if request.include_exploits:
533
+ report["exploit_scenarios"] = _get_exploit_scenarios(
534
+ source_code, request.task_id
535
+ )
536
+
537
  return report
538
+
539
  except Exception as exc:
540
  raise HTTPException(status_code=400, detail=str(exc))
541
 
542
 
543
+ @app.get("/dashboard")
544
  def get_dashboard() -> Dict[str, Any]:
545
  """Get dashboard overview of all contract categories."""
546
  try:
 
549
  "total_samples": 18,
550
  "categories": 3,
551
  "avg_risk_score": 0.65,
552
+ "last_updated": "2026-04-06T12:00:00Z",
553
  },
554
  "category_breakdown": {
555
  "task_1_best_practices": {
556
  "sample_count": 6,
557
  "avg_severity": "Low",
558
+ "common_issues": [
559
+ "missing_spdx",
560
+ "old_compiler_version",
561
+ "missing_natspec",
562
+ ],
563
  },
564
  "task_2_gas_optimization": {
565
+ "sample_count": 6,
566
  "avg_severity": "Medium",
567
+ "common_issues": [
568
+ "unbounded_loop",
569
+ "redundant_storage_read",
570
+ "poor_struct_packing",
571
+ ],
572
  },
573
  "task_3_security": {
574
  "sample_count": 6,
575
+ "avg_severity": "Critical",
576
+ "common_issues": [
577
+ "reentrancy",
578
+ "missing_access_control",
579
+ "tx_origin_auth",
580
+ ],
581
+ },
582
+ "task_4_comprehensive_audit": {
583
+ "sample_count": 3,
584
+ "avg_severity": "Critical",
585
+ "common_issues": [
586
+ "old_compiler_version",
587
+ "unbounded_loop",
588
+ "tx_origin_auth",
589
+ ],
590
+ },
591
  },
592
  "agent_stats": {
593
  "multi_agent_enabled": True,
594
  "analyzer_accuracy": 0.85,
595
  "verifier_precision": 0.90,
596
+ "risk_scorer_coverage": 0.95,
597
+ },
598
  }
599
+
600
  return dashboard_data
601
+
602
  except Exception as exc:
603
  raise HTTPException(status_code=400, detail=str(exc))
604
 
605
 
606
  def _calculate_risk_metrics(source_code: str, task_id: str) -> Dict[str, Any]:
607
  """Calculate comprehensive risk metrics for a contract."""
608
+
609
  # Basic metrics
610
+ lines_of_code = len(source_code.split("\n"))
611
+ cyclomatic_complexity = (
612
+ source_code.count("if ")
613
+ + source_code.count("for ")
614
+ + source_code.count("while ")
615
+ )
616
+
617
  # Security indicators
618
+ has_external_calls = "call{" in source_code or ".call(" in source_code
619
+ has_state_variables = "mapping(" in source_code or "uint" in source_code
620
+ has_payable = "payable" in source_code
621
+
622
  # Calculate overall risk score
623
  base_risk = 0.3
624
  if task_id == "task_3_security":
625
  base_risk = 0.8
626
+ elif task_id == "task_4_comprehensive_audit":
627
+ base_risk = 0.85
628
+ elif task_id == "task_2_gas_optimization":
629
  base_risk = 0.5
630
+
631
  complexity_factor = min(cyclomatic_complexity * 0.1, 0.3)
632
  external_call_factor = 0.2 if has_external_calls else 0.0
633
  payable_factor = 0.1 if has_payable else 0.0
634
+
635
+ overall_risk = min(
636
+ base_risk + complexity_factor + external_call_factor + payable_factor, 1.0
637
+ )
638
+
639
  return {
640
  "overall_risk_score": round(overall_risk, 3),
641
+ "risk_category": "High"
642
+ if overall_risk >= 0.7
643
+ else "Medium"
644
+ if overall_risk >= 0.4
645
+ else "Low",
646
  "complexity_score": cyclomatic_complexity,
647
  "lines_of_code": lines_of_code,
648
  "has_external_calls": has_external_calls,
649
  "has_state_variables": has_state_variables,
650
  "has_payable_functions": has_payable,
651
+ "recommended_review_time": f"{max(15, lines_of_code * 2)} minutes",
652
  }
653
 
654
 
655
  def _generate_recommendations(risk_metrics: Dict[str, Any]) -> List[str]:
656
  """Generate audit recommendations based on risk metrics."""
657
+
658
  recommendations = []
659
+
660
  if risk_metrics["overall_risk_score"] >= 0.7:
661
  recommendations.append("🔴 HIGH RISK: Requires immediate security review")
662
  recommendations.append("Consider formal verification for critical functions")
663
+
664
  if risk_metrics["has_external_calls"]:
665
+ recommendations.append(
666
+ "⚠️ External calls detected: Review for reentrancy vulnerabilities"
667
+ )
668
+
669
  if risk_metrics["has_payable_functions"]:
670
+ recommendations.append(
671
+ "💰 Payable functions detected: Ensure proper access controls"
672
+ )
673
+
674
  if risk_metrics["complexity_score"] > 10:
675
+ recommendations.append(
676
+ "🧩 High complexity: Consider breaking into smaller functions"
677
+ )
678
+
679
  if risk_metrics["lines_of_code"] > 100:
680
  recommendations.append("📏 Large contract: Consider modularization")
681
+
682
  recommendations.append("✅ Run static analysis tools (Slither, Mythril)")
683
  recommendations.append("🧪 Implement comprehensive test coverage")
684
+
685
  return recommendations
686
 
687
 
688
  def _get_fix_suggestions(source_code: str, task_id: str) -> List[Dict[str, str]]:
689
  """Get specific fix suggestions for common issues."""
690
+
691
  suggestions = []
692
+
693
  if not source_code.strip().startswith("// SPDX"):
694
+ suggestions.append(
695
+ {
696
+ "issue": "Missing SPDX License",
697
+ "fix": "Add '// SPDX-License-Identifier: MIT' at the top of the file",
698
+ "priority": "Low",
699
+ }
700
+ )
701
+
702
  if "0.4." in source_code or "0.7." in source_code:
703
+ suggestions.append(
704
+ {
705
+ "issue": "Outdated Solidity Version",
706
+ "fix": "Update to 'pragma solidity ^0.8.0;' for better security",
707
+ "priority": "Medium",
708
+ }
709
+ )
710
+
711
  if "tx.origin" in source_code:
712
+ suggestions.append(
713
+ {
714
+ "issue": "tx.origin Usage",
715
+ "fix": "Replace 'tx.origin' with 'msg.sender' for proper authentication",
716
+ "priority": "High",
717
+ }
718
+ )
719
+
720
  return suggestions
721
 
722
 
723
  def _get_exploit_scenarios(source_code: str, task_id: str) -> List[Dict[str, str]]:
724
  """Get potential exploit scenarios for security issues."""
725
+
726
  scenarios = []
727
+
728
  if "call{" in source_code and "balances[" in source_code:
729
+ scenarios.append(
730
+ {
731
+ "vulnerability": "Reentrancy Attack",
732
+ "scenario": "Attacker creates malicious contract with fallback function that calls withdraw() recursively",
733
+ "impact": "Complete drainage of contract funds",
734
+ "mitigation": "Implement checks-effects-interactions pattern or ReentrancyGuard",
735
+ }
736
+ )
737
+
738
  if "tx.origin" in source_code:
739
+ scenarios.append(
740
+ {
741
+ "vulnerability": "tx.origin Phishing",
742
+ "scenario": "Attacker tricks user into calling malicious contract that forwards transactions",
743
+ "impact": "Unauthorized access to protected functions",
744
+ "mitigation": "Use msg.sender instead of tx.origin for authentication",
745
+ }
746
+ )
747
+
748
  return scenarios
749
 
750
+
751
  def main():
752
  import uvicorn
753
+
754
  uvicorn.run("server.app:app", host="0.0.0.0", port=7860)
755
 
756
+
757
  if __name__ == "__main__":
758
  main()