Coverage for src/alprina_cli/agents/web3_auditor/solidity_analyzer.py: 11%

428 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-14 11:27 +0100

1""" 

2Solidity Smart Contract Static Analyzer 

3 

4Inspired by Slither but enhanced for startup Web3 security needs. 

5Focuses on OWASP Smart Contract Top 10 detection with economic context. 

6""" 

7 

8import re 

9import ast 

10from pathlib import Path 

11from typing import List, Dict, Any, Optional, Tuple 

12from dataclasses import dataclass 

13from enum import Enum 

14 

15class VulnerabilityType(Enum): 

16 REENTRANCY = "reentrancy" 

17 ACCESS_CONTROL = "access_control" 

18 INTEGER_OVERFLOW_UNDERFLOW = "integer_overflow" 

19 UNCHECKED_LOW_LEVEL_CALL = "unchecked_call" 

20 LOGIC_ERROR = "logic_error" 

21 TIMESTAMP_DEPENDENCE = "timestamp_dependence" 

22 UNINITIALIZED_STORAGE = "uninitialized_storage" 

23 ORACLE_MANIPULATION = "oracle_manipulation" 

24 GAS_LIMIT_ISSUE = "gas_limit" 

25 DENIAL_OF_SERVICE = "denial_of_service" 

26 

27@dataclass 

28class SolidityVulnerability: 

29 """Represents a smart contract vulnerability""" 

30 vulnerability_type: VulnerabilityType 

31 severity: str # "critical", "high", "medium", "low" 

32 title: str 

33 description: str 

34 file_path: str 

35 line_number: Optional[int] 

36 function_name: Optional[str] 

37 contract_name: str 

38 code_snippet: Optional[str] = None 

39 remediation: Optional[str] = None 

40 confidence: int = 100 # 0-100 

41 

42class SolidityStaticAnalyzer: 

43 """ 

44 Enhanced Solidity analyzer focused on Web3 startup security needs 

45 """ 

46 

47 def __init__(self): 

48 self.vulnerability_patterns = self._initialize_patterns() 

49 self.contract_structure = None 

50 self.functions = [] 

51 self.state_variables = [] 

52 

53 def analyze_contract(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

54 """ 

55 Comprehensive smart contract vulnerability analysis 

56  

57 Args: 

58 contract_code: Solidity source code 

59 file_path: Path to the contract file 

60  

61 Returns: 

62 List of detected vulnerabilities 

63 """ 

64 vulnerabilities = [] 

65 

66 try: 

67 # Parse contract structure 

68 self._parse_contract_structure(contract_code) 

69 

70 # Run comprehensive vulnerability detection 

71 reentrancy_vulns = self._detect_reentrancy_vulnerabilities(contract_code, file_path) 

72 access_control_vulns = self._detect_access_control_vulnerabilities(contract_code, file_path) 

73 overflow_vulns = self._detect_integer_vulnerabilities(contract_code, file_path) 

74 call_vulns = self._detect_unchecked_calls(contract_code, file_path) 

75 logic_vulns = self._detect_logic_errors(contract_code, file_path) 

76 oracle_vulns = self._detect_oracle_manipulation(contract_code, file_path) 

77 input_vulns = self._detect_input_validation_issues(contract_code, file_path) 

78 

79 vulnerabilities.extend(reentrancy_vulns) 

80 vulnerabilities.extend(access_control_vulns) 

81 vulnerabilities.extend(overflow_vulns) 

82 vulnerabilities.extend(call_vulns) 

83 vulnerabilities.extend(logic_vulns) 

84 vulnerabilities.extend(oracle_vulns) 

85 vulnerabilities.extend(input_vulns) 

86 

87 except Exception as e: 

88 # Add parsing error as info-level vulnerability 

89 vulnerabilities.append(SolidityVulnerability( 

90 vulnerability_type=VulnerabilityType.LOGIC_ERROR, 

91 severity="low", 

92 title="Analysis Error", 

93 description=f"Could not fully analyze contract: {str(e)}", 

94 file_path=file_path, 

95 line_number=None, 

96 function_name=None, 

97 contract_name="unknown", 

98 confidence=20 

99 )) 

100 

101 return vulnerabilities 

102 

103 def _parse_contract_structure(self, contract_code: str): 

104 """Parse contract structure to identify functions and state variables""" 

105 lines = contract_code.split('\n') 

106 

107 # Find contracts 

108 self.contract_structure = { 

109 'contracts': [], 

110 'functions': [], 

111 'state_variables': [] 

112 } 

113 

114 current_contract = None 

115 

116 for i, line in enumerate(lines): 

117 line = line.strip() 

118 

119 # Skip comments and empty lines 

120 if not line or line.startswith('//') or line.startswith('/*'): 

121 continue 

122 

123 # Find contract definitions 

124 if line.startswith('contract ') or line.startswith('abstract contract '): 

125 contract_match = re.search(r'(abstract )?contract\s+(\w+)', line) 

126 if contract_match: 

127 current_contract = contract_match.group(2) 

128 self.contract_structure['contracts'].append({ 

129 'name': current_contract, 

130 'line': i + 1 

131 }) 

132 continue 

133 

134 # Find function definitions 

135 if current_contract and ('function ' in line or 'modifier ' in line): 

136 func_match = re.search(r'(function|modifier)\s+(\w+)', line) 

137 if func_match: 

138 func_type = func_match.group(1) 

139 func_name = func_match.group(2) 

140 function_info = { 

141 'name': func_name, 

142 'type': func_type, 

143 'contract': current_contract, 

144 'line': i + 1, 

145 'visibility': 'internal' # Default 

146 } 

147 

148 # Check visibility modifiers 

149 if 'public' in line: 

150 function_info['visibility'] = 'public' 

151 elif 'external' in line: 

152 function_info['visibility'] = 'external' 

153 elif 'internal' in line: 

154 function_info['visibility'] = 'internal' 

155 elif 'private' in line: 

156 function_info['visibility'] = 'private' 

157 

158 # Check for payable 

159 if 'payable' in line: 

160 function_info['payable'] = True 

161 

162 self.contract_structure['functions'].append(function_info) 

163 continue 

164 

165 # Find state variables 

166 if current_contract and ('uint256 ' in line or 'address ' in line or 'mapping(' in line): 

167 # Extract variable name 

168 var_match = re.search(r'(uint256|address|mapping)\s+(?:\(.*?\))?\s*(\w+)', line) 

169 if var_match: 

170 var_type = var_match.group(1) 

171 var_name = var_match.group(2) 

172 self.contract_structure['state_variables'].append({ 

173 'name': var_name, 

174 'type': var_type, 

175 'contract': current_contract, 

176 'line': i + 1 

177 }) 

178 

179 def _detect_reentrancy_vulnerabilities(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

180 """Detect reentrancy attack vulnerabilities""" 

181 vulnerabilities = [] 

182 lines = contract_code.split('\n') 

183 

184 for i, line in enumerate(lines): 

185 line_content = line.strip() 

186 if not line_content or line_content.startswith('//'): 

187 continue 

188 

189 # Pattern 1: Call to external address before state change 

190 if ('.call(' in line_content or '.transfer(' in line_content or '.send(' in line_content): 

191 # Check if this happens before state update in same function 

192 vuln = SolidityVulnerability( 

193 vulnerability_type=VulnerabilityType.REENTRANCY, 

194 severity="high", 

195 title="Potential Reentrancy", 

196 description=f"External call detected: {line_content[:80]}... Reentrancy vulnerability if state changes happen after this call.", 

197 file_path=file_path, 

198 line_number=i + 1, 

199 function_name=None, 

200 contract_name=self._get_current_function_contract(i, lines), 

201 code_snippet=line_content.strip(), 

202 remediation="Implement checks-effects-interactions pattern or use ReentrancyGuard modifier", 

203 confidence=75 

204 ) 

205 vulnerabilities.append(vuln) 

206 

207 # Pattern 2: Low-level calls without checks 

208 if '.call.value(' in line_content and 'return' not in line_content: 

209 vuln = SolidityVulnerability( 

210 vulnerability_type=VulnerabilityType.UNCHECKED_LOW_LEVEL_CALL, 

211 severity="medium", 

212 title="Unchecked Low-Level Call", 

213 description=f"Unverified low-level call: {line_content[:80]}...", 

214 file_path=file_path, 

215 line_number=i + 1, 

216 function_name=None, 

217 contract_name=self._get_current_function_contract(i, lines), 

218 code_snippet=line_content.strip(), 

219 remediation="Always check return values of low-level calls", 

220 confidence=85 

221 ) 

222 vulnerabilities.append(vuln) 

223 

224 return vulnerabilities 

225 

226 def _detect_access_control_vulnerabilities(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

227 """Detect access control vulnerabilities""" 

228 vulnerabilities = [] 

229 lines = contract_code.split('\n') 

230 

231 critical_functions = ['withdraw', 'transferOwnership', 'mint', 'burn', 'pause', 'unpause'] 

232 

233 for i, line in enumerate(lines): 

234 line_content = line.strip() 

235 if not line_content or line_content.startswith('//'): 

236 continue 

237 

238 # Check for critical functions without access controls 

239 for func_name in critical_functions: 

240 if f'function {func_name}' in line_content and 'public' in line_content: 

241 # Look for modifiers in the same line 

242 if not any(mod in line_content for mod in ['onlyOwner', 'require', 'if', 'modifier']): 

243 vuln = SolidityVulnerability( 

244 vulnerability_type=VulnerabilityType.ACCESS_CONTROL, 

245 severity="critical", 

246 title="Missing Access Control", 

247 description=f"Critical function {func_name} lacks proper access control modifier", 

248 file_path=file_path, 

249 line_number=i + 1, 

250 function_name=func_name, 

251 contract_name=self._get_current_function_contract(i, lines), 

252 code_snippet=line_content.strip(), 

253 remediation=f"Add access control modifier (e.g., onlyOwner) to {func_name} function", 

254 confidence=90 

255 ) 

256 vulnerabilities.append(vuln) 

257 

258 # Pattern: owner() function that returns hardcoded address 

259 for i, line in enumerate(lines): 

260 line_content = line.strip() 

261 if 'return' in line_content and '0x' in line_content: 

262 if 'owner()' in ''.join(lines[max(0, i-2):i+2]): 

263 vuln = SolidityVulnerability( 

264 vulnerability_type=VulnerabilityType.ACCESS_CONTROL, 

265 severity="medium", 

266 title="Hardcoded Owner Address", 

267 description="Owner function returns hardcoded address instead of dynamic storage", 

268 file_path=file_path, 

269 line_number=i + 1, 

270 function_name=None, 

271 contract_name=self._get_current_function_contract(i, lines), 

272 code_snippet=line_content.strip(), 

273 remediation="Use address storage variable for owner instead of hardcoded value", 

274 confidence=70 

275 ) 

276 vulnerabilities.append(vuln) 

277 

278 return vulnerabilities 

279 

280 def _detect_integer_vulnerabilities(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

281 """Detect integer overflow/underflow vulnerabilities""" 

282 vulnerabilities = [] 

283 lines = contract_code.split('\n') 

284 

285 arithmetic_operations = ['+', '-', '*', '/'] 

286 

287 for i, line in enumerate(lines): 

288 line_content = line.strip() 

289 if not line_content or line_content.startswith('//'): 

290 continue 

291 

292 # Look for arithmetic operations without SafeMath 

293 for op in arithmetic_operations: 

294 if op in line_content and 'SafeMath' not in ''.join(lines[max(0, i-5):i+5]): 

295 # Check if this is a critical operation (balance, amount, etc.) 

296 context_words = ['balance', 'amount', 'total', 'supply', 'price', 'value'] 

297 if any(word in line_content.lower() for word in context_words): 

298 vuln = SolidityVulnerability( 

299 vulnerability_type=VulnerabilityType.INTEGER_OVERFLOW_UNDERFLOW, 

300 severity="medium", 

301 title="Potential Integer Overflow/Underflow", 

302 description=f"Arithmetic operation without overflow protection: {line_content[:80]}...", 

303 file_path=file_path, 

304 line_number=i + 1, 

305 function_name=None, 

306 contract_name=self._get_current_function_contract(i, lines), 

307 code_snippet=line_content.strip(), 

308 remediation="Use SafeMath library or Solidity 0.8+ which has built-in overflow protection", 

309 confidence=65 

310 ) 

311 vulnerabilities.append(vuln) 

312 

313 return vulnerabilities 

314 

315 def _detect_unchecked_calls(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

316 """Detect unchecked external calls""" 

317 vulnerabilities = [] 

318 lines = contract_code.split('\n') 

319 

320 for i, line in enumerate(lines): 

321 line_content = line.strip() 

322 if not line_content or line_content.startswith('//'): 

323 continue 

324 

325 # External call patterns 

326 external_calls = ['.call(', '.delegatecall(', '.transfer(', '.send('] 

327 

328 for pattern in external_calls: 

329 if pattern in line_content: 

330 # Check if return value is being used or verified 

331 next_lines = lines[i+1:i+3] # Look at next 2-3 lines 

332 has_check = any('require(' in next_line or 'if (' in next_line 

333 for next_line in next_lines if next_line.strip()) 

334 

335 if not has_check: 

336 vuln = SolidityVulnerability( 

337 vulnerability_type=VulnerabilityType.UNCHECKED_LOW_LEVEL_CALL, 

338 severity="high", 

339 title="Unchecked External Call", 

340 description=f"External call {pattern} without return value verification", 

341 file_path=file_path, 

342 line_number=i + 1, 

343 function_name=None, 

344 contract_name=self._get_current_function_contract(i, lines), 

345 code_snippet=line_content.strip(), 

346 remediation="Always verify return values of external calls", 

347 confidence=80 

348 ) 

349 vulnerabilities.append(vuln) 

350 

351 return vulnerabilities 

352 

353 def _detect_logic_errors(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

354 """Detect logic errors and bad practices""" 

355 vulnerabilities = [] 

356 lines = contract_code.split('\n') 

357 

358 # Pattern 1: Using block.timestamp for critical operations 

359 for i, line in enumerate(lines): 

360 line_content = line.strip() 

361 if 'block.timestamp' in line_content: 

362 # Check if timestamp is used for something critical 

363 critical_contexts = ['deadline', 'expiration', 'unlock', 'vest'] 

364 if any(context in ''.join(lines[max(0, i-3):i+3]).lower() for context in critical_contexts): 

365 vuln = SolidityVulnerability( 

366 vulnerability_type=VulnerabilityType.TIMESTAMP_DEPENDENCE, 

367 severity="medium", 

368 title="Block Timestamp Manipulation Risk", 

369 description="Using block.timestamp for critical logic that miners can manipulate", 

370 file_path=file_path, 

371 line_number=i + 1, 

372 function_name=None, 

373 contract_name=self._get_current_function_contract(i, lines), 

374 code_snippet=line_content.strip(), 

375 remediation="Use block.number or external oracle for time-dependent logic", 

376 confidence=75 

377 ) 

378 vulnerabilities.append(vuln) 

379 

380 # Pattern 2: Uninitialized storage pointers 

381 for i, line in enumerate(lines): 

382 line_content = line.strip() 

383 if 'Storage(' in line_content and 'new' in line_content: 

384 vuln = SolidityVulnerability( 

385 vulnerability_type=VulnerabilityType.UNINITIALIZED_STORAGE, 

386 severity="medium", 

387 title="Potential Uninitialized Storage Pointer", 

388 description="Creating struct or array storage without proper initialization", 

389 file_path=file_path, 

390 line_number=i + 1, 

391 function_name=None, 

392 contract_name=self._get_current_function_contract(i, lines), 

393 code_snippet=line_content.strip(), 

394 remediation="Initialize storage variables properly before use", 

395 confidence=60 

396 ) 

397 vulnerabilities.append(vuln) 

398 

399 return vulnerabilities 

400 

401 def _detect_oracle_manipulation(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

402 """ 

403 Detect price oracle manipulation vulnerabilities 

404 

405 WEEK 2 DAY 1: Enhanced Oracle Manipulation Detection 

406 Based on OWASP SC02:2025 and 2024-2025 exploit research 

407 

408 Detection Patterns: 

409 1. Chainlink oracle staleness (no updatedAt check) 

410 2. Single oracle source (no aggregation) 

411 3. Missing price bounds validation 

412 4. UniswapV2 spot price usage (flash loan vulnerable) 

413 5. Missing TWAP implementation 

414 6. No oracle failure handling (try/catch) 

415 7. Direct pool reserve usage 

416 """ 

417 vulnerabilities = [] 

418 lines = contract_code.split('\n') 

419 

420 # Track oracle usage per function for contextual analysis 

421 function_contexts = self._extract_function_contexts(lines) 

422 

423 # Pattern 1: Chainlink oracle without staleness checks 

424 chainlink_vulns = self._detect_chainlink_staleness(lines, file_path, function_contexts) 

425 vulnerabilities.extend(chainlink_vulns) 

426 

427 # Pattern 2: Single oracle source without aggregation 

428 single_oracle_vulns = self._detect_single_oracle_usage(lines, file_path, function_contexts) 

429 vulnerabilities.extend(single_oracle_vulns) 

430 

431 # Pattern 3: Missing price bounds validation 

432 bounds_vulns = self._detect_missing_price_bounds(lines, file_path, function_contexts) 

433 vulnerabilities.extend(bounds_vulns) 

434 

435 # Pattern 4: UniswapV2 spot price vulnerability 

436 uniswap_vulns = self._detect_uniswap_spot_price(lines, file_path, function_contexts) 

437 vulnerabilities.extend(uniswap_vulns) 

438 

439 # Pattern 5: Pool reserve manipulation 

440 reserve_vulns = self._detect_pool_reserve_manipulation(lines, file_path, function_contexts) 

441 vulnerabilities.extend(reserve_vulns) 

442 

443 # Pattern 6: Missing oracle failure handling 

444 failure_vulns = self._detect_missing_oracle_failure_handling(lines, file_path, function_contexts) 

445 vulnerabilities.extend(failure_vulns) 

446 

447 return vulnerabilities 

448 

449 def _extract_function_contexts(self, lines: List[str]) -> Dict[int, Dict[str, Any]]: 

450 """Extract function contexts for contextual analysis""" 

451 contexts = {} 

452 current_function = None 

453 current_contract = None 

454 brace_count = 0 

455 

456 for i, line in enumerate(lines): 

457 line_stripped = line.strip() 

458 

459 # Track contract 

460 if line_stripped.startswith('contract ') or line_stripped.startswith('abstract contract '): 

461 match = re.search(r'contract\s+(\w+)', line_stripped) 

462 if match: 

463 current_contract = match.group(1) 

464 

465 # Track function 

466 if line_stripped.startswith('function '): 

467 match = re.search(r'function\s+(\w+)', line_stripped) 

468 if match: 

469 current_function = match.group(1) 

470 brace_count = 0 

471 

472 # Track braces for function scope 

473 brace_count += line_stripped.count('{') - line_stripped.count('}') 

474 

475 # Store context 

476 contexts[i] = { 

477 'function': current_function, 

478 'contract': current_contract, 

479 'in_function': brace_count > 0 and current_function is not None 

480 } 

481 

482 # Reset function when it ends 

483 if brace_count == 0 and current_function is not None: 

484 current_function = None 

485 

486 return contexts 

487 

488 def _detect_chainlink_staleness( 

489 self, 

490 lines: List[str], 

491 file_path: str, 

492 contexts: Dict[int, Dict[str, Any]] 

493 ) -> List[SolidityVulnerability]: 

494 """ 

495 Detect Chainlink oracle usage without staleness checks 

496 

497 CVE Pattern: Missing updatedAt validation 

498 Real Exploits: Polter Finance, BonqDAO Protocol 

499 """ 

500 vulnerabilities = [] 

501 

502 # Pattern: latestRoundData() without updatedAt check 

503 chainlink_patterns = [ 

504 r'latestRoundData\s*\(', 

505 r'AggregatorV3Interface', 

506 r'getRoundData\s*\(', 

507 ] 

508 

509 for i, line in enumerate(lines): 

510 line_content = line.strip() 

511 

512 # Check if line contains Chainlink oracle call 

513 if any(re.search(pattern, line_content) for pattern in chainlink_patterns): 

514 context = contexts.get(i, {}) 

515 function_name = context.get('function', 'unknown') 

516 contract_name = context.get('contract', 'unknown') 

517 

518 # Check next 15 lines for staleness validation 

519 check_window = lines[i:i+15] 

520 has_staleness_check = any( 

521 'updatedAt' in check_line and 

522 ('block.timestamp' in check_line or 'now' in check_line) 

523 for check_line in check_window 

524 ) 

525 

526 has_price_validation = any( 

527 'price' in check_line and 

528 ('require' in check_line or 'revert' in check_line) and 

529 ('>' in check_line or '<' in check_line) 

530 for check_line in check_window 

531 ) 

532 

533 if not has_staleness_check: 

534 vulnerabilities.append(SolidityVulnerability( 

535 vulnerability_type=VulnerabilityType.ORACLE_MANIPULATION, 

536 severity="high", 

537 title="Chainlink Oracle Staleness Not Checked", 

538 description=( 

539 f"Function '{function_name}' uses Chainlink oracle without validating " 

540 f"data freshness. Stale price data can be exploited for profit. " 

541 f"OWASP SC02:2025 - Price Oracle Manipulation. " 

542 f"Similar to Polter Finance exploit (2024)." 

543 ), 

544 file_path=file_path, 

545 line_number=i + 1, 

546 function_name=function_name, 

547 contract_name=contract_name, 

548 code_snippet=line_content, 

549 remediation=( 

550 "Add staleness validation:\n" 

551 "require(block.timestamp - updatedAt < 3600, 'Stale price');\n" 

552 "Also validate: updatedAt > 0, answeredInRound >= roundId, price > 0" 

553 ), 

554 confidence=90 

555 )) 

556 

557 if not has_price_validation: 

558 vulnerabilities.append(SolidityVulnerability( 

559 vulnerability_type=VulnerabilityType.ORACLE_MANIPULATION, 

560 severity="medium", 

561 title="Missing Chainlink Price Validation", 

562 description=( 

563 f"Function '{function_name}' doesn't validate price from Chainlink. " 

564 f"Price should be checked for: price > 0, within bounds." 

565 ), 

566 file_path=file_path, 

567 line_number=i + 1, 

568 function_name=function_name, 

569 contract_name=contract_name, 

570 code_snippet=line_content, 

571 remediation=( 

572 "Add price validation:\n" 

573 "require(price > 0, 'Invalid price');\n" 

574 "require(price >= minPrice && price <= maxPrice, 'Price out of bounds');" 

575 ), 

576 confidence=85 

577 )) 

578 

579 return vulnerabilities 

580 

581 def _detect_single_oracle_usage( 

582 self, 

583 lines: List[str], 

584 file_path: str, 

585 contexts: Dict[int, Dict[str, Any]] 

586 ) -> List[SolidityVulnerability]: 

587 """ 

588 Detect single oracle source without aggregation 

589 

590 OWASP Recommendation: Use multiple independent oracle sources 

591 """ 

592 vulnerabilities = [] 

593 

594 # Track oracle sources per function 

595 function_oracle_counts = {} 

596 

597 oracle_source_patterns = [ 

598 r'latestRoundData\s*\(', # Chainlink 

599 r'getAmountsOut\s*\(', # Uniswap 

600 r'consult\s*\(', # TWAP 

601 r'\.price\s*\(', # Generic price getter 

602 ] 

603 

604 for i, line in enumerate(lines): 

605 line_content = line.strip() 

606 context = contexts.get(i, {}) 

607 function_name = context.get('function') 

608 

609 if not function_name or not context.get('in_function'): 

610 continue 

611 

612 # Count oracle sources 

613 for pattern in oracle_source_patterns: 

614 if re.search(pattern, line_content): 

615 if function_name not in function_oracle_counts: 

616 function_oracle_counts[function_name] = { 

617 'count': 0, 

618 'line': i + 1, 

619 'contract': context.get('contract', 'unknown') 

620 } 

621 function_oracle_counts[function_name]['count'] += 1 

622 

623 # Report functions with single oracle source 

624 for func_name, data in function_oracle_counts.items(): 

625 if data['count'] == 1: 

626 vulnerabilities.append(SolidityVulnerability( 

627 vulnerability_type=VulnerabilityType.ORACLE_MANIPULATION, 

628 severity="medium", 

629 title="Single Oracle Source - No Aggregation", 

630 description=( 

631 f"Function '{func_name}' relies on single oracle source. " 

632 f"OWASP SC02:2025 recommends multiple independent oracles " 

633 f"to prevent single-point manipulation. " 

634 f"$70M+ lost to oracle manipulation in 2024." 

635 ), 

636 file_path=file_path, 

637 line_number=data['line'], 

638 function_name=func_name, 

639 contract_name=data['contract'], 

640 code_snippet=None, 

641 remediation=( 

642 "Implement multi-oracle strategy:\n" 

643 "1. Use Chainlink + UniswapV3 TWAP\n" 

644 "2. Compare prices and revert if deviation > 10%\n" 

645 "3. Take median of 3+ oracle sources" 

646 ), 

647 confidence=80 

648 )) 

649 

650 return vulnerabilities 

651 

652 def _detect_missing_price_bounds( 

653 self, 

654 lines: List[str], 

655 file_path: str, 

656 contexts: Dict[int, Dict[str, Any]] 

657 ) -> List[SolidityVulnerability]: 

658 """ 

659 Detect missing MIN_PRICE and MAX_PRICE validation 

660 

661 OWASP Mitigation: Implement price boundaries 

662 """ 

663 vulnerabilities = [] 

664 

665 price_usage_pattern = r'(price|amount|value)\s*[=:]' 

666 

667 for i, line in enumerate(lines): 

668 line_content = line.strip() 

669 context = contexts.get(i, {}) 

670 

671 if not context.get('in_function'): 

672 continue 

673 

674 # Check if line assigns/uses price 

675 if re.search(price_usage_pattern, line_content): 

676 # Look for oracle calls in previous 5 lines 

677 prev_lines = lines[max(0, i-5):i+1] 

678 has_oracle_call = any( 

679 'latestRoundData' in prev or 

680 'getAmountOut' in prev or 

681 'consult' in prev 

682 for prev in prev_lines 

683 ) 

684 

685 if not has_oracle_call: 

686 continue 

687 

688 # Check for bounds validation in next 10 lines 

689 next_lines = lines[i+1:i+10] 

690 has_min_check = any('MIN' in next_line.upper() or 'minPrice' in next_line for next_line in next_lines) 

691 has_max_check = any('MAX' in next_line.upper() or 'maxPrice' in next_line for next_line in next_lines) 

692 

693 if not (has_min_check and has_max_check): 

694 vulnerabilities.append(SolidityVulnerability( 

695 vulnerability_type=VulnerabilityType.ORACLE_MANIPULATION, 

696 severity="medium", 

697 title="Missing Price Bounds Validation", 

698 description=( 

699 f"Price usage at line {i+1} lacks MIN/MAX bounds validation. " 

700 f"OWASP SC02:2025 recommends price thresholds to detect anomalies. " 

701 f"Without bounds, extreme price manipulation goes undetected." 

702 ), 

703 file_path=file_path, 

704 line_number=i + 1, 

705 function_name=context.get('function', 'unknown'), 

706 contract_name=context.get('contract', 'unknown'), 

707 code_snippet=line_content, 

708 remediation=( 

709 "Add price bounds:\n" 

710 "uint256 constant MIN_PRICE = 1e6; // Adjust for token\n" 

711 "uint256 constant MAX_PRICE = 1e12;\n" 

712 "require(price >= MIN_PRICE && price <= MAX_PRICE, 'Price anomaly');" 

713 ), 

714 confidence=75 

715 )) 

716 

717 return vulnerabilities 

718 

719 def _detect_uniswap_spot_price( 

720 self, 

721 lines: List[str], 

722 file_path: str, 

723 contexts: Dict[int, Dict[str, Any]] 

724 ) -> List[SolidityVulnerability]: 

725 """ 

726 Detect UniswapV2 spot price usage (flash loan vulnerable) 

727 

728 Critical: Spot prices can be manipulated within single transaction 

729 Real Exploits: Moby (Jan 2025), The Vow (Aug 2024) 

730 """ 

731 vulnerabilities = [] 

732 

733 # Patterns indicating spot price usage 

734 spot_price_patterns = [ 

735 r'getAmountsOut\s*\(', 

736 r'getAmountOut\s*\(', 

737 r'getReserves\s*\(', 

738 r'\.reserves\(', 

739 r'pair\.getReserves', 

740 ] 

741 

742 twap_patterns = [ 

743 r'consult\s*\(', 

744 r'TWAP', 

745 r'timeWeighted', 

746 r'observe\s*\(', # UniswapV3 

747 ] 

748 

749 for i, line in enumerate(lines): 

750 line_content = line.strip() 

751 context = contexts.get(i, {}) 

752 

753 # Check if using spot price 

754 is_spot_price = any(re.search(pattern, line_content) for pattern in spot_price_patterns) 

755 

756 if not is_spot_price: 

757 continue 

758 

759 # Check if TWAP is also used (good) 

760 check_window = lines[max(0, i-10):i+10] 

761 has_twap = any( 

762 any(re.search(twap_pattern, check_line) for twap_pattern in twap_patterns) 

763 for check_line in check_window 

764 ) 

765 

766 if not has_twap: 

767 vulnerabilities.append(SolidityVulnerability( 

768 vulnerability_type=VulnerabilityType.ORACLE_MANIPULATION, 

769 severity="critical", 

770 title="UniswapV2 Spot Price Flash Loan Vulnerability", 

771 description=( 

772 f"Line {i+1} uses Uniswap spot price without TWAP protection. " 

773 f"CRITICAL: Spot prices can be manipulated within single transaction. " 

774 f"Recent Exploits: Moby (Jan 2025), The Vow (Aug 2024). " 

775 f"Attackers use flash loans to manipulate pool reserves. " 

776 f"OWASP SC02:2025 - Most common DeFi exploit pattern (34.3%)." 

777 ), 

778 file_path=file_path, 

779 line_number=i + 1, 

780 function_name=context.get('function', 'unknown'), 

781 contract_name=context.get('contract', 'unknown'), 

782 code_snippet=line_content, 

783 remediation=( 

784 "CRITICAL FIX REQUIRED:\n" 

785 "1. Use UniswapV3 TWAP with observe() for time-weighted prices\n" 

786 "2. OR use Chainlink as primary oracle with Uniswap as backup\n" 

787 "3. Never rely on spot prices for critical logic\n" 

788 "4. Implement price deviation checks between oracles" 

789 ), 

790 confidence=95 

791 )) 

792 

793 return vulnerabilities 

794 

795 def _detect_pool_reserve_manipulation( 

796 self, 

797 lines: List[str], 

798 file_path: str, 

799 contexts: Dict[int, Dict[str, Any]] 

800 ) -> List[SolidityVulnerability]: 

801 """ 

802 Detect direct pool reserve usage for pricing 

803 

804 Using pool reserves directly is extremely vulnerable to manipulation 

805 """ 

806 vulnerabilities = [] 

807 

808 reserve_patterns = [ 

809 r'reserve0', 

810 r'reserve1', 

811 r'\.reserves\s*\(', 

812 r'balanceOf\(address\(this\)\)', 

813 r'token\.balanceOf\(pool\)', 

814 ] 

815 

816 for i, line in enumerate(lines): 

817 line_content = line.strip() 

818 context = contexts.get(i, {}) 

819 

820 if not context.get('in_function'): 

821 continue 

822 

823 # Check if using reserves for calculation 

824 uses_reserves = any(re.search(pattern, line_content) for pattern in reserve_patterns) 

825 

826 if uses_reserves and ('*' in line_content or '/' in line_content or '=' in line_content): 

827 # Check if it's in a price calculation context 

828 next_lines = lines[i:i+5] 

829 looks_like_price_calc = any( 

830 'price' in next_line.lower() or 

831 'value' in next_line.lower() or 

832 'amount' in next_line.lower() 

833 for next_line in next_lines 

834 ) 

835 

836 if looks_like_price_calc: 

837 vulnerabilities.append(SolidityVulnerability( 

838 vulnerability_type=VulnerabilityType.ORACLE_MANIPULATION, 

839 severity="critical", 

840 title="Pool Reserve Direct Usage - Flash Loan Vulnerability", 

841 description=( 

842 f"Line {i+1} uses pool reserves directly for pricing. " 

843 f"CRITICAL: Reserves can be manipulated within single transaction. " 

844 f"This is the #1 DeFi exploit pattern. " 

845 f"Using pool balances as price oracle is NEVER safe." 

846 ), 

847 file_path=file_path, 

848 line_number=i + 1, 

849 function_name=context.get('function', 'unknown'), 

850 contract_name=context.get('contract', 'unknown'), 

851 code_snippet=line_content, 

852 remediation=( 

853 "CRITICAL FIX:\n" 

854 "1. Never use pool reserves directly for pricing\n" 

855 "2. Use Chainlink Price Feeds for external prices\n" 

856 "3. Use UniswapV3 TWAP with sufficient time window (30+ min)\n" 

857 "4. Implement multi-oracle aggregation" 

858 ), 

859 confidence=95 

860 )) 

861 

862 return vulnerabilities 

863 

864 def _detect_missing_oracle_failure_handling( 

865 self, 

866 lines: List[str], 

867 file_path: str, 

868 contexts: Dict[int, Dict[str, Any]] 

869 ) -> List[SolidityVulnerability]: 

870 """ 

871 Detect oracle calls without try/catch blocks 

872 

873 Oracle failures can DOS contracts if not handled properly 

874 """ 

875 vulnerabilities = [] 

876 

877 oracle_call_patterns = [ 

878 r'latestRoundData\s*\(', 

879 r'getRoundData\s*\(', 

880 ] 

881 

882 for i, line in enumerate(lines): 

883 line_content = line.strip() 

884 context = contexts.get(i, {}) 

885 

886 # Check if making oracle call 

887 is_oracle_call = any(re.search(pattern, line_content) for pattern in oracle_call_patterns) 

888 

889 if not is_oracle_call: 

890 continue 

891 

892 # Check if wrapped in try/catch 

893 prev_lines = lines[max(0, i-3):i] 

894 has_try = any('try' in prev_line for prev_line in prev_lines) 

895 

896 next_lines = lines[i+1:i+5] 

897 has_catch = any('catch' in next_line for next_line in next_lines) 

898 

899 if not (has_try and has_catch): 

900 vulnerabilities.append(SolidityVulnerability( 

901 vulnerability_type=VulnerabilityType.ORACLE_MANIPULATION, 

902 severity="medium", 

903 title="Missing Oracle Failure Handling", 

904 description=( 

905 f"Oracle call at line {i+1} lacks try/catch error handling. " 

906 f"Oracle failures can cause contract DOS. " 

907 f"Best practice: wrap oracle calls in try/catch with fallback logic." 

908 ), 

909 file_path=file_path, 

910 line_number=i + 1, 

911 function_name=context.get('function', 'unknown'), 

912 contract_name=context.get('contract', 'unknown'), 

913 code_snippet=line_content, 

914 remediation=( 

915 "Add error handling:\n" 

916 "try oracle.latestRoundData() returns (...) {\n" 

917 " // use data\n" 

918 "} catch {\n" 

919 " // fallback logic or revert gracefully\n" 

920 "}" 

921 ), 

922 confidence=70 

923 )) 

924 

925 return vulnerabilities 

926 

927 def _detect_input_validation_issues(self, contract_code: str, file_path: str) -> List[SolidityVulnerability]: 

928 """ 

929 Detect input validation vulnerabilities 

930 

931 WEEK 2 DAY 2: Enhanced Input Validation Detection 

932 Based on OWASP Smart Contract Top 10 2025 - $14.6M in losses 

933 

934 Detection Patterns: 

935 1. Missing address(0) checks for address parameters 

936 2. Missing zero/negative amount checks 

937 3. Missing array bounds validation 

938 4. Unchecked low-level call return values (enhanced) 

939 5. Missing parameter validation in critical functions 

940 6. Unsafe type conversions 

941 """ 

942 vulnerabilities = [] 

943 lines = contract_code.split('\n') 

944 

945 # Extract function contexts for analysis 

946 function_contexts = self._extract_function_contexts(lines) 

947 

948 # Pattern 1: Missing address(0) validation 

949 address_vulns = self._detect_missing_address_validation(lines, file_path, function_contexts) 

950 vulnerabilities.extend(address_vulns) 

951 

952 # Pattern 2: Missing amount/value validation 

953 amount_vulns = self._detect_missing_amount_validation(lines, file_path, function_contexts) 

954 vulnerabilities.extend(amount_vulns) 

955 

956 # Pattern 3: Missing array bounds validation 

957 array_vulns = self._detect_missing_array_bounds(lines, file_path, function_contexts) 

958 vulnerabilities.extend(array_vulns) 

959 

960 # Pattern 4: Enhanced unchecked external calls 

961 external_call_vulns = self._detect_unchecked_external_calls(lines, file_path, function_contexts) 

962 vulnerabilities.extend(external_call_vulns) 

963 

964 # Pattern 5: Unsafe type conversions 

965 conversion_vulns = self._detect_unsafe_type_conversions(lines, file_path, function_contexts) 

966 vulnerabilities.extend(conversion_vulns) 

967 

968 return vulnerabilities 

969 

970 def _detect_missing_address_validation( 

971 self, 

972 lines: List[str], 

973 file_path: str, 

974 contexts: Dict[int, Dict[str, Any]] 

975 ) -> List[SolidityVulnerability]: 

976 """ 

977 Detect missing address(0) validation 

978 

979 OWASP: Lack of Input Validation ($14.6M in losses) 

980 Critical Pattern: Sending funds to address(0) = permanent loss 

981 """ 

982 vulnerabilities = [] 

983 

984 # Track function parameters 

985 for i, line in enumerate(lines): 

986 line_content = line.strip() 

987 context = contexts.get(i, {}) 

988 

989 # Check if it's a function definition with address parameter 

990 if line_content.startswith('function '): 

991 # Extract parameters 

992 if '(' in line_content and ')' in line_content: 

993 params_match = re.search(r'\((.*?)\)', line_content) 

994 if params_match: 

995 params_str = params_match.group(1) 

996 

997 # Find address parameters 

998 address_params = re.findall(r'address\s+(\w+)', params_str) 

999 

1000 if address_params: 

1001 function_name = context.get('function', 'unknown') 

1002 

1003 # Check next 20 lines for address(0) validation 

1004 check_window = lines[i+1:i+20] 

1005 

1006 for addr_param in address_params: 

1007 has_validation = any( 

1008 f'{addr_param}' in check_line and 

1009 ('address(0)' in check_line or '0x0' in check_line) and 

1010 ('require' in check_line or 'revert' in check_line or 'if' in check_line) 

1011 for check_line in check_window 

1012 ) 

1013 

1014 # Check if it's used in critical operations 

1015 is_critical = any( 

1016 f'{addr_param}' in check_line and 

1017 any(op in check_line for op in ['transfer', 'send', 'call', 'delegatecall', '=']) 

1018 for check_line in check_window 

1019 ) 

1020 

1021 if not has_validation and is_critical: 

1022 vulnerabilities.append(SolidityVulnerability( 

1023 vulnerability_type=VulnerabilityType.LOGIC_ERROR, 

1024 severity="high", 

1025 title="Missing Address Zero Validation", 

1026 description=( 

1027 f"Parameter '{addr_param}' in function '{function_name}' lacks address(0) check. " 

1028 f"OWASP 2025: Lack of Input Validation ($14.6M in losses). " 

1029 f"Funds sent to address(0) are permanently lost - no private key exists. " 

1030 f"This is a common attack vector in 2024-2025." 

1031 ), 

1032 file_path=file_path, 

1033 line_number=i + 1, 

1034 function_name=function_name, 

1035 contract_name=context.get('contract', 'unknown'), 

1036 code_snippet=line_content, 

1037 remediation=( 

1038 f"Add validation:\n" 

1039 f"require({addr_param} != address(0), 'Zero address not allowed');" 

1040 ), 

1041 confidence=85 

1042 )) 

1043 

1044 return vulnerabilities 

1045 

1046 def _detect_missing_amount_validation( 

1047 self, 

1048 lines: List[str], 

1049 file_path: str, 

1050 contexts: Dict[int, Dict[str, Any]] 

1051 ) -> List[SolidityVulnerability]: 

1052 """ 

1053 Detect missing amount/value validation (zero or negative) 

1054 

1055 Common Pattern: Functions accepting amounts without validation 

1056 """ 

1057 vulnerabilities = [] 

1058 

1059 for i, line in enumerate(lines): 

1060 line_content = line.strip() 

1061 context = contexts.get(i, {}) 

1062 

1063 # Check if it's a function with amount/value parameter 

1064 if line_content.startswith('function '): 

1065 if '(' in line_content and ')' in line_content: 

1066 params_match = re.search(r'\((.*?)\)', line_content) 

1067 if params_match: 

1068 params_str = params_match.group(1) 

1069 

1070 # Find amount/value parameters (uint256, uint, int) 

1071 amount_params = re.findall( 

1072 r'(?:uint256|uint|int256|int)\s+(\w*(?:amount|value|quantity|balance|size)\w*)', 

1073 params_str, 

1074 re.IGNORECASE 

1075 ) 

1076 

1077 if amount_params: 

1078 function_name = context.get('function', 'unknown') 

1079 

1080 # Check next 15 lines for validation 

1081 check_window = lines[i+1:i+15] 

1082 

1083 for amount_param in amount_params: 

1084 has_validation = any( 

1085 f'{amount_param}' in check_line and 

1086 ('> 0' in check_line or '!= 0' in check_line or '>=' in check_line) and 

1087 ('require' in check_line or 'revert' in check_line) 

1088 for check_line in check_window 

1089 ) 

1090 

1091 if not has_validation: 

1092 vulnerabilities.append(SolidityVulnerability( 

1093 vulnerability_type=VulnerabilityType.LOGIC_ERROR, 

1094 severity="medium", 

1095 title="Missing Amount Validation", 

1096 description=( 

1097 f"Parameter '{amount_param}' in function '{function_name}' lacks validation. " 

1098 f"Should check: amount > 0 to prevent zero-value operations. " 

1099 f"OWASP 2025: Input Validation ($14.6M in losses)." 

1100 ), 

1101 file_path=file_path, 

1102 line_number=i + 1, 

1103 function_name=function_name, 

1104 contract_name=context.get('contract', 'unknown'), 

1105 code_snippet=line_content, 

1106 remediation=( 

1107 f"Add validation:\n" 

1108 f"require({amount_param} > 0, 'Amount must be greater than zero');" 

1109 ), 

1110 confidence=75 

1111 )) 

1112 

1113 return vulnerabilities 

1114 

1115 def _detect_missing_array_bounds( 

1116 self, 

1117 lines: List[str], 

1118 file_path: str, 

1119 contexts: Dict[int, Dict[str, Any]] 

1120 ) -> List[SolidityVulnerability]: 

1121 """ 

1122 Detect missing array bounds validation 

1123 

1124 Pattern: Array access without length check 

1125 """ 

1126 vulnerabilities = [] 

1127 

1128 for i, line in enumerate(lines): 

1129 line_content = line.strip() 

1130 context = contexts.get(i, {}) 

1131 

1132 if not context.get('in_function'): 

1133 continue 

1134 

1135 # Check for array access patterns 

1136 array_access_pattern = r'(\w+)\[(\w+|\d+)\]' 

1137 matches = re.findall(array_access_pattern, line_content) 

1138 

1139 for array_name, index in matches: 

1140 # Skip if index is a number 

1141 if index.isdigit(): 

1142 continue 

1143 

1144 # Check if there's a bounds check before this line 

1145 prev_lines = lines[max(0, i-5):i] 

1146 has_bounds_check = any( 

1147 f'{index}' in prev_line and 

1148 (f'{array_name}.length' in prev_line or 'length' in prev_line) and 

1149 ('require' in prev_line or 'if' in prev_line or '<' in prev_line) 

1150 for prev_line in prev_lines 

1151 ) 

1152 

1153 if not has_bounds_check: 

1154 vulnerabilities.append(SolidityVulnerability( 

1155 vulnerability_type=VulnerabilityType.LOGIC_ERROR, 

1156 severity="medium", 

1157 title="Missing Array Bounds Validation", 

1158 description=( 

1159 f"Array access '{array_name}[{index}]' lacks bounds checking. " 

1160 f"Out-of-bounds access causes revert but wastes gas. " 

1161 f"OWASP 2025: Input Validation." 

1162 ), 

1163 file_path=file_path, 

1164 line_number=i + 1, 

1165 function_name=context.get('function', 'unknown'), 

1166 contract_name=context.get('contract', 'unknown'), 

1167 code_snippet=line_content, 

1168 remediation=( 

1169 f"Add bounds check:\n" 

1170 f"require({index} < {array_name}.length, 'Index out of bounds');" 

1171 ), 

1172 confidence=70 

1173 )) 

1174 

1175 return vulnerabilities 

1176 

1177 def _detect_unchecked_external_calls( 

1178 self, 

1179 lines: List[str], 

1180 file_path: str, 

1181 contexts: Dict[int, Dict[str, Any]] 

1182 ) -> List[SolidityVulnerability]: 

1183 """ 

1184 Detect unchecked external calls (enhanced) 

1185 

1186 OWASP 2025: Unchecked External Calls ($550.7K in losses) 

1187 Climbed from #10 to #6 in 2025 rankings 

1188 

1189 Pattern: Low-level calls (.call, .delegatecall) without return value check 

1190 """ 

1191 vulnerabilities = [] 

1192 

1193 low_level_calls = [ 

1194 r'\.call\s*\(', 

1195 r'\.delegatecall\s*\(', 

1196 r'\.staticcall\s*\(', 

1197 ] 

1198 

1199 for i, line in enumerate(lines): 

1200 line_content = line.strip() 

1201 context = contexts.get(i, {}) 

1202 

1203 if not context.get('in_function'): 

1204 continue 

1205 

1206 # Check for low-level calls 

1207 for pattern in low_level_calls: 

1208 if re.search(pattern, line_content): 

1209 # Check if return value is captured 

1210 captures_return = re.search(r'(?:bool\s+\w+|[\(\w]+)\s*=.*\.(?:call|delegatecall|staticcall)', line_content) 

1211 

1212 if captures_return: 

1213 # Check if the captured value is validated 

1214 # Extract the variable name 

1215 var_match = re.search(r'(?:bool\s+(\w+)|[\(](\w+))', line_content) 

1216 if var_match: 

1217 var_name = var_match.group(1) or var_match.group(2) 

1218 

1219 # Check next 5 lines for require/if with this variable 

1220 check_window = lines[i+1:i+5] 

1221 has_validation = any( 

1222 var_name in check_line and 

1223 ('require' in check_line or 'if' in check_line or 'assert' in check_line) 

1224 for check_line in check_window 

1225 ) 

1226 

1227 if not has_validation: 

1228 vulnerabilities.append(SolidityVulnerability( 

1229 vulnerability_type=VulnerabilityType.UNCHECKED_LOW_LEVEL_CALL, 

1230 severity="high", 

1231 title="Unchecked External Call Return Value", 

1232 description=( 

1233 f"Low-level call return value '{var_name}' captured but not validated. " 

1234 f"OWASP 2025 #6: Unchecked External Calls ($550.7K in losses). " 

1235 f"Climbed from #10 to #6 in 2025 rankings. " 

1236 f"Failed external calls can cause unexpected behavior if not handled." 

1237 ), 

1238 file_path=file_path, 

1239 line_number=i + 1, 

1240 function_name=context.get('function', 'unknown'), 

1241 contract_name=context.get('contract', 'unknown'), 

1242 code_snippet=line_content, 

1243 remediation=( 

1244 f"Add validation:\n" 

1245 f"require({var_name}, 'External call failed');\n" 

1246 f"// OR use try/catch for better error handling" 

1247 ), 

1248 confidence=90 

1249 )) 

1250 else: 

1251 # Return value not even captured! 

1252 vulnerabilities.append(SolidityVulnerability( 

1253 vulnerability_type=VulnerabilityType.UNCHECKED_LOW_LEVEL_CALL, 

1254 severity="critical", 

1255 title="External Call Return Value Ignored", 

1256 description=( 

1257 f"Low-level call return value completely ignored. " 

1258 f"CRITICAL: OWASP 2025 #6 ($550.7K in losses). " 

1259 f"The call may fail silently causing logic errors or fund loss. " 

1260 f"Always capture and validate external call results." 

1261 ), 

1262 file_path=file_path, 

1263 line_number=i + 1, 

1264 function_name=context.get('function', 'unknown'), 

1265 contract_name=context.get('contract', 'unknown'), 

1266 code_snippet=line_content, 

1267 remediation=( 

1268 "Capture and validate return value:\n" 

1269 "(bool success, ) = target.call(...);\n" 

1270 "require(success, 'External call failed');" 

1271 ), 

1272 confidence=95 

1273 )) 

1274 

1275 return vulnerabilities 

1276 

1277 def _detect_unsafe_type_conversions( 

1278 self, 

1279 lines: List[str], 

1280 file_path: str, 

1281 contexts: Dict[int, Dict[str, Any]] 

1282 ) -> List[SolidityVulnerability]: 

1283 """ 

1284 Detect unsafe type conversions 

1285 

1286 Pattern: Converting between types without validation 

1287 """ 

1288 vulnerabilities = [] 

1289 

1290 conversion_patterns = [ 

1291 r'uint256\s*\(\s*int', # int to uint 

1292 r'uint\s*\(\s*int', 

1293 r'int\s*\(\s*uint', # uint to int 

1294 r'address\s*\(\s*uint', # uint to address 

1295 ] 

1296 

1297 for i, line in enumerate(lines): 

1298 line_content = line.strip() 

1299 context = contexts.get(i, {}) 

1300 

1301 if not context.get('in_function'): 

1302 continue 

1303 

1304 for pattern in conversion_patterns: 

1305 if re.search(pattern, line_content): 

1306 # Check if there's validation nearby 

1307 check_window = lines[max(0, i-2):i+3] 

1308 has_validation = any( 

1309 'require' in check_line or 'assert' in check_line 

1310 for check_line in check_window 

1311 ) 

1312 

1313 if not has_validation: 

1314 vulnerabilities.append(SolidityVulnerability( 

1315 vulnerability_type=VulnerabilityType.LOGIC_ERROR, 

1316 severity="medium", 

1317 title="Unsafe Type Conversion", 

1318 description=( 

1319 f"Type conversion without validation at line {i+1}. " 

1320 f"Converting between signed/unsigned or numeric/address types can cause unexpected behavior. " 

1321 f"OWASP 2025: Input Validation." 

1322 ), 

1323 file_path=file_path, 

1324 line_number=i + 1, 

1325 function_name=context.get('function', 'unknown'), 

1326 contract_name=context.get('contract', 'unknown'), 

1327 code_snippet=line_content, 

1328 remediation=( 

1329 "Add validation before conversion:\n" 

1330 "require(value >= 0, 'Invalid conversion');\n" 

1331 "// OR use SafeCast library for safe conversions" 

1332 ), 

1333 confidence=70 

1334 )) 

1335 

1336 return vulnerabilities 

1337 

1338 def _get_current_function_contract(self, line_index: int, lines: List[str]) -> str: 

1339 """Helper to determine current contract context""" 

1340 current_contract = "unknown" 

1341 

1342 # Look backwards to find most recent contract 

1343 for i in range(line_index, -1, -1): 

1344 line = lines[i].strip() 

1345 if line.startswith('contract ') or line.startswith('abstract contract '): 

1346 match = re.search(r'contract\s+(\w+)', line) 

1347 if match: 

1348 current_contract = match.group(1) 

1349 break 

1350 # Stop looking if we hit another contract boundary 

1351 if line.startswith('contract ') and i < line_index: 

1352 break 

1353 

1354 return current_contract 

1355 

1356 def _initialize_patterns(self) -> Dict[str, List[str]]: 

1357 """Initialize vulnerability pattern detectors""" 

1358 return { 

1359 'reentrancy': [ 

1360 r'\.call\(', 

1361 r'\.transfer\(', 

1362 r'\.send\(' 

1363 ], 

1364 'access_control': [ 

1365 r'function\s+\w+\s*public', 

1366 r'missing.*modifier', 

1367 r'no.*access.*control' 

1368 ], 

1369 'integer_overflow': [ 

1370 r'[\+\-\*\/]', 

1371 r'(balance|amount|total|supply).*[\+\-\*\/]' 

1372 ], 

1373 'unchecked_call': [ 

1374 r'\.call\(', 

1375 r'\.delegatecall\(' 

1376 ], 

1377 'oracle_manipulation': [ 

1378 r'uniswap.*router', 

1379 r'price.*oracle', 

1380 r'getAmountOut' 

1381 ] 

1382 }