Coverage for src/alprina_cli/agents/web3_auditor/defi_risk_assessor.py: 16%

170 statements  

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

1""" 

2DeFi Economic Risk Assessor with AI Enhancement 

3 

4Analyzes smart contracts for economic attack vectors that traditional static analysis misses. 

5Uses both pattern matching and LLM-powered contextual analysis. 

6""" 

7 

8import re 

9from typing import List, Dict, Any, Optional 

10from dataclasses import dataclass 

11from enum import Enum 

12 

13class EconomicRiskType(Enum): 

14 FLASH_LOAN_ATTACK = "flash_loan_attack" 

15 PRICE_ORACLE_MANIPULATION = "price_oracle_manipulation" 

16 LIQUIDITY_DRAIN = "liquidity_drain" 

17 MEV_EXTRACTION = "mev_extraction" 

18 YIELD_FARMING_EXPLOIT = "yield_farming_exploit" 

19 CROSS_CHAIN_BRIDGE = "cross_chain_bridge" 

20 TOKEN_MINT_MANIPULATION = "token_mint_manipulation" 

21 GOVERNANCE_ATTACK = "governance_attack" 

22 

23@dataclass 

24class EconomicRisk: 

25 """Represents an economic risk in a DeFi protocol""" 

26 risk_type: EconomicRiskType 

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

28 title: str 

29 description: str 

30 attack_scenario: str 

31 file_path: str 

32 line_number: Optional[int] 

33 contract_name: str 

34 economic_impact: str 

35 remediation: str 

36 confidence: int = 80 # 0-100 

37 

38class DeFiRiskAssessor: 

39 """ 

40 AI-powered DeFi economic risk assessor 

41 Goes beyond code vulnerabilities to detect business logic flaws 

42 """ 

43 

44 def __init__(self): 

45 self.risk_patterns = self._initialize_risk_patterns() 

46 self.llm_client = self._initialize_llm_client() 

47 

48 def assess_economic_risks(self, contract_code: str, protocol_context: Dict[str, Any]) -> List[EconomicRisk]: 

49 """ 

50 Comprehensive economic risk assessment for DeFi protocols 

51  

52 Args: 

53 contract_code: Solidity or other blockchain contract code 

54 protocol_context: Information about the DeFi protocol 

55  

56 Returns: 

57 List of detected economic risks 

58 """ 

59 risks = [] 

60 

61 # Traditional pattern-based analysis 

62 flash_loan_risks = self._detect_flash_loan_vulnerabilities(contract_code, protocol_context) 

63 oracle_risks = self._detect_price_oracle_risks(contract_code, protocol_context) 

64 liquidity_risks = self._detect_liquidity_drain_risks(contract_code, protocol_context) 

65 governance_risks = self._detect_governance_attacks(contract_code, protocol_context) 

66 

67 risks.extend(flash_loan_risks) 

68 risks.extend(oracle_risks) 

69 risks.extend(liquidity_risks) 

70 risks.extend(governance_risks) 

71 

72 # AI-enhanced analysis for complex patterns 

73 llm_risks = self._llm_economic_analysis(contract_code, protocol_context) 

74 risks.extend(llm_risks) 

75 

76 return risks 

77 

78 def _detect_flash_loan_vulnerabilities(self, contract_code: str, context: Dict[str, Any]) -> List[EconomicRisk]: 

79 """Detect flash loan attack vectors""" 

80 risks = [] 

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

82 

83 for i, line in enumerate(lines): 

84 line_content = line.strip() 

85 

86 # Pattern 1: Functions that update state based on token balances 

87 if any(pattern in line_content for pattern in ['balanceOf', 'totalSupply', 'reserve']): 

88 # Check if this is a liquidity function 

89 next_lines = lines[i+1:i+10] # Look at next several lines 

90 function_block = '\n'.join(next_lines) 

91 

92 if 'update' in function_block.lower() or 'calculate' in function_block.lower(): 

93 # Look for missing reentrancy checks or price validation 

94 has_protection = any( 

95 protection in function_block.lower() 

96 for protection in ['reentrancyguard', 'nonreentrant', 'require(msg.value'] 

97 ) 

98 

99 if not has_protection: 

100 risk = EconomicRisk( 

101 risk_type=EconomicRiskType.FLASH_LOAN_ATTACK, 

102 severity="high", 

103 title="Flash Loan Manipulation Risk", 

104 description="Function updates state based on balances without flash loan protection", 

105 attack_scenario="Attacker takes flash loan → Manipulates token prices → Exploits price-dependent calculation → Repays loan with profit", 

106 file_path=context.get('file_path', 'unknown'), 

107 line_number=i + 1, 

108 contract_name=context.get('contract_name', 'unknown'), 

109 economic_impact="Potential protocol fund drain if exploited", 

110 remediation="Add reentrancy protection and validate price calculations with time-weighted averages", 

111 confidence=75 

112 ) 

113 risks.append(risk) 

114 

115 # Pattern 2: Liquidity provision functions with price calculation 

116 liquidity_patterns = ['addLiquidity', 'removeLiquidity', 'swap', 'exchange'] 

117 for pattern in liquidity_patterns: 

118 context_window = contract_code[max(0, i-5):i+5] 

119 if pattern in context_window and 'calculate' in context_window: 

120 # Check for price oracle usage 

121 if 'uniswap' in context_window.lower() or 'getAmountsOut' in context_window: 

122 risk = EconomicRisk( 

123 risk_type=EconomicRiskType.FLASH_LOAN_ATTACK, 

124 severity="high", 

125 title="Flash Loan Oracle Manipulation", 

126 description="Liquidity function uses on-chain price oracle vulnerable to manipulation", 

127 attack_scenario="Attaker uses flash loan to manipulate exchange rate → Drains liquidity pool → Repays loan", 

128 file_path=context.get('file_path', 'unknown'), 

129 line_number=i + 1, 

130 contract_name=context.get('contract_name', 'unknown'), 

131 economic_impact="Complete liquidity pool drain possible", 

132 remediation="Use TWAP price oracle or validate against multiple sources", 

133 confidence=80 

134 ) 

135 risks.append(risk) 

136 

137 return risks 

138 

139 def _detect_price_oracle_risks(self, contract_code: str, context: Dict[str, Any]) -> List[EconomicRisk]: 

140 """Detect price oracle manipulation vulnerabilities""" 

141 risks = [] 

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

143 

144 oracle_sources = ['uniswap', 'sushiswap', 'curve', 'balancer', 'getAmountsOut'] 

145 

146 for i, line in enumerate(lines): 

147 line_content = line.strip() 

148 

149 # Check for direct oracle usage without validation 

150 for oracle in oracle_sources: 

151 if oracle in line_content.lower(): 

152 # Look at surrounding code for price usage 

153 context_lines = lines[max(0, i-3):i+8] # 3 lines before, 7 after 

154 context_block = '\n'.join(context_lines) 

155 

156 # Check for price-dependent calculations 

157 if any(calc in context_block for calc in ['amount', 'price', 'value', 'calculate']): 

158 # Check for validation 

159 has_validation = any( 

160 validation in context_block.lower() 

161 for validation in ['min', 'max', '>=', '<=', 'require'] 

162 ) 

163 

164 if not has_validation: 

165 risk = EconomicRisk( 

166 risk_type=EconomicRiskType.PRICE_ORACLE_MANIPULATION, 

167 severity="high", 

168 title="Unvalidated Price Oracle", 

169 description=f"Using {oracle.title()} price oracle without validation", 

170 attack_scenario="Attacker manipulates pool reserves → Invalid price data → Protocol miscalculates → Economic loss", 

171 file_path=context.get('file_path', 'unknown'), 

172 line_number=i + 1, 

173 contract_name=context.get('contract_name', 'unknown'), 

174 economic_impact="Protocol calculates wrong prices, leading to fund loss", 

175 remediation="Implement price deviation checks and use multiple oracle sources", 

176 confidence=85 

177 ) 

178 risks.append(risk) 

179 

180 return risks 

181 

182 def _detect_liquidity_drain_risks(self, contract_code: str, context: Dict[str, Any]) -> List[EconomicRisk]: 

183 """Detect liquidity draining attack vectors""" 

184 risks = [] 

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

186 

187 # Pattern: Functions that provide liquidity without restrictions 

188 liquidity_patterns = [ 

189 'function.*withdraw', 

190 'function.*redeem', 

191 'function.*claim', 

192 'function.*removeLiquidity' 

193 ] 

194 

195 for i, line in enumerate(lines): 

196 line_content = line.strip() 

197 

198 for pattern in liquidity_patterns: 

199 if re.search(pattern, line_content): 

200 # Look for access controls 

201 function_context = lines[max(0, i-2):i+20] # Function scope 

202 function_block = '\n'.join(function_context) 

203 

204 has_auth = any( 

205 auth in function_block.lower() 

206 for auth in ['onlyowner', 'require', 'modifiers'] 

207 ) 

208 

209 has_amount_validation = any( 

210 validation in function_block.lower() 

211 for validation in ['require.*amount', 'min.*amount', '< balance'] 

212 ) 

213 

214 if not has_auth or not has_amount_validation: 

215 risk = EconomicRisk( 

216 risk_type=EconomicRiskType.LIQUIDITY_DRAIN, 

217 severity="critical", 

218 title="Liquidity Drain Vulnerability", 

219 description="Liquidity withdrawal function lacking proper controls", 

220 attack_scenario="Attacker calls unauthorized withdrawal → Drains all available liquidity → Protocol becomes insolvent", 

221 file_path=context.get('file_path', 'unknown'), 

222 line_number=i + 1, 

223 contract_name=context.get('contract_name', 'unknown'), 

224 economic_impact="Complete fund loss through liquidity drain", 

225 remediation="Add proper access controls, amount validations, and withdrawal limits", 

226 confidence=90 

227 ) 

228 risks.append(risk) 

229 

230 return risks 

231 

232 def _detect_governance_attacks(self, contract_code: str, context: Dict[str, Any]) -> List[EconomicRisk]: 

233 """Detect governance system attack vectors""" 

234 risks = [] 

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

236 

237 governance_patterns = [ 

238 'function vote', 

239 'function propose', 

240 'function execute', 

241 'function delegate' 

242 ] 

243 

244 for i, line in enumerate(lines): 

245 line_content = line.strip() 

246 

247 for pattern in governance_patterns: 

248 if pattern in line_content: 

249 # Check for voting power manipulation risks 

250 context_lines = lines[max(0, i-3):i+15] # Goverance function scope 

251 context_block = '\n'.join(context_lines) 

252 

253 # Look for token balance usage without time-lock 

254 if ('balance' in context_block and 'vote' in context_block.lower()): 

255 has_timelock = any( 

256 timelock in context_block.lower() 

257 for timelock in ['timelock', 'delay', 'waiting', 'period'] 

258 ) 

259 

260 if not has_timelock: 

261 risk = EconomicRisk( 

262 risk_type=EconomicRiskType.GOVERNANCE_ATTACK, 

263 severity="high", 

264 title="Governance Flash Loan Attack", 

265 description="Voting mechanism vulnerable to flash loan manipulation", 

266 attack_scenario="Attaker takes flash loan → Gains voting power → Passes malicious proposal → Executes against protocol → Repays loan", 

267 file_path=context.get('file_path', 'unknown'), 

268 line_number=i + 1, 

269 contract_name=context.get('contract_name', 'unknown'), 

270 economic_impact="Protocol governance can be seized, leading to full protocol control", 

271 remediation="Implement voting power time-locks and minimum holding periods", 

272 confidence=80 

273 ) 

274 risks.append(risk) 

275 

276 return risks 

277 

278 def _llm_economic_analysis(self, contract_code: str, context: Dict[str, Any]) -> List[EconomicRisk]: 

279 """AI-powered economic risk analysis for complex attack vectors""" 

280 risks = [] 

281 

282 try: 

283 # Prepare context for LLM analysis 

284 protocol_type = context.get('protocol_type', 'DeFi Protocol') 

285 contract_name = context.get('contract_name', 'Smart Contract') 

286 

287 prompt = f""" 

288 Analyze this DeFi smart contract for complex economic attack vectors that traditional static analysis might miss: 

289  

290 Protocol Type: {protocol_type} 

291 Contract Name: {contract_name} 

292  

293 Contract Code: 

294 {contract_code} 

295  

296 Focus on these economic attack patterns: 

297 1. Flash Loan Attack Vectors - identify where price calculations could be manipulated 

298 2. Cross-Protocol Arbitrage Exploits - look for interactions with other DeFi protocols 

299 3. MEV (Maximal Extractable Value) opportunities - identify transaction ordering exploits 

300 4. Token Supply/Governance Manipulation - check for voting/influence manipulation 

301 5. Liquidity Manipulation Scenarios - identify pool manipulation possibilities 

302 6. Cross-Chain Bridge Risks - if applicable, check for multi-chain vulnerabilities 

303  

304 For each risk identified, provide: 

305 - Attack scenario in step-by-step format 

306 - Economic impact assessment  

307 - Mitigation strategies 

308  

309 Analyze deeply and think about complex multi-step attacks. 

310 """ 

311 

312 # Simulate LLM response (in production, this would call actual LLM) 

313 llm_response = self._simulate_llm_analysis(contract_code, context) 

314 

315 if llm_response: 

316 # Parse LLM response into EconomicRisk objects 

317 llm_risks = self._parse_llm_response(llm_response, contract_code, context) 

318 risks.extend(llm_risks) 

319 

320 except Exception as e: 

321 # Add fallback risk if LLM analysis fails 

322 fallback_risk = EconomicRisk( 

323 risk_type=EconomicRiskType.LIQUIDITY_DRAIN, 

324 severity="medium", 

325 title="AI Analysis Limitation", 

326 description=f"Complete economic analysis unavailable: {str(e)[:100]}", 

327 attack_scenario="Consider manual security audit for complex economic vectors", 

328 file_path=context.get('file_path', 'unknown'), 

329 line_number=None, 

330 contract_name=context.get('contract_name', 'unknown'), 

331 economic_impact="Unknown - manual review recommended", 

332 remediation="Manual security audit recommended for comprehensive economic analysis", 

333 confidence=30 

334 ) 

335 risks.append(fallback_risk) 

336 

337 return risks 

338 

339 def _simulate_llm_analysis(self, contract_code: str, context: Dict[str, Any]) -> Optional[str]: 

340 """Simulate LLM analysis - in production, this would call actual LLM API""" 

341 

342 # Check for common DeFi patterns and return simulated responses 

343 contract_lower = contract_code.lower() 

344 

345 if 'uniswap' in contract_lower and 'getamountsout' in contract_lower: 

346 return """ 

347 RISK: Flash Loan Oracle Manipulation 

348 Severity: High 

349  

350 Description: This contract uses Uniswap price oracle (getAmountsOut) without validation, making it vulnerable to flash loan manipulation attacks. 

351  

352 Attack Scenario: 

353 1. Attaker identifies large reserves in Uniswap pool 

354 2. Takes flash loan from Aave/Compound 

355 3. Uses flash loan to manipulate pool prices 

356 4. Calls protocol function using manipulated oracle prices 

357 5. Protocol calculates incorrect prices, allowing arbitrage 

358 6. Attaker repays flash loan and keeps profit 

359  

360 Economic Impact: Complete liquidity drain possible 

361  

362 Mitigation: Implement TWAP price oracle, validate maximum price deviations 

363 """ 

364 

365 elif 'token' in contract_lower and 'mint' in contract_lower: 

366 return """ 

367 RISK: Token Supply Manipulation 

368 Severity: High 

369  

370 Description: Minting function lacks proper access controls, potentially allowing unlimited token creation. 

371  

372 Attack Scenario: 

373 1. Attaker identifies minting privilege escalation 

374 2. Mints large number of tokens 

375 3. Uses tokens to manipulate voting/proportions 

376 4. Drains platform resources through voting power 

377 5. Results in protocol fund loss 

378  

379 Economic Impact: Protocol governance and fund control loss 

380  

381 Mitigation: Add strict access controls, implement minting caps, add governance delays 

382 """ 

383 

384 elif 'withdraw' in contract_lower and 'balance' in contract_lower: 

385 return """ 

386 RISK: Liquidity Drain Attack 

387 Severity: Critical 

388  

389 Description: Withdrawal function lacks adequate access controls and balance validations. 

390  

391 Attack Scenario: 

392 1. Attaker identifies unprotected withdrawal function 

393 2. Calls withdrawal to drain available funds 

394 3. Protocol becomes insolvent or drained completely 

395 4. All other users lose access to funds 

396  

397 Economic Impact: Complete fund loss possible 

398  

399 Mitigation: Implement withdrawal limits, access controls, and balance validations 

400 """ 

401 

402 return None 

403 

404 def _parse_llm_response(self, llm_response: str, contract_code: str, context: Dict[str, Any]) -> List[EconomicRisk]: 

405 """Parse LLM response into EconomicRisk objects""" 

406 risks = [] 

407 

408 try: 

409 # Parse simulated response format 

410 if 'RISK:' in llm_response: 

411 sections = llm_response.split('RISK:')[1:] 

412 

413 for section in sections: 

414 lines = section.strip().split('\n') 

415 if len(lines) < 5: 

416 continue 

417 

418 # Extract key information 

419 title = lines[0].split(':')[1].strip() if ':' in lines[0] else "Economic Risk" 

420 severity = lines[1].split(':')[1].strip().lower() if ':' in lines[1] else "medium" 

421 description = lines[2].split(':')[1].strip() if ':' in lines[2] else "Economic vulnerability detected" 

422 

423 # Find attack scenario 

424 attack_scenario = "" 

425 description_start = False 

426 for line in lines[3:]: 

427 if line.strip().startswith("Attack Scenario:"): 

428 description_start = True 

429 attack_scenario = line.split("Attack Scenario:")[1].strip() 

430 elif description_start: 

431 if line.strip().startswith("Economic Impact:"): 

432 break 

433 attack_scenario += " " + line.strip() 

434 

435 # Determine risk type based on content 

436 risk_type = EconomicRiskType.LIQUIDITY_DRAIN # default 

437 if "flash loan" in llm_response.lower(): 

438 risk_type = EconomicRiskType.FLASH_LOAN_ATTACK 

439 elif "oracle" in llm_response.lower() or "price" in llm_response.lower(): 

440 risk_type = EconomicRiskType.PRICE_ORACLE_MANIPULATION 

441 elif "token" in llm_response.lower() or "mint" in llm_response.lower(): 

442 risk_type = EconomicRiskType.TOKEN_MINT_MANIPULATION 

443 

444 # Extract economic impact and mitigation 

445 economic_impact = "Potential economic loss" 

446 remediation = "Security audit recommended" 

447 

448 for line in lines: 

449 if line.strip().startswith("Economic Impact:"): 

450 economic_impact = line.split("Economic Impact:")[1].strip() 

451 elif line.strip().startswith("Mitigation:"): 

452 remediation = line.split("Mitigation:")[1].strip() 

453 

454 risk = EconomicRisk( 

455 risk_type=risk_type, 

456 severity=severity if severity in ["critical", "high", "medium", "low"] else "medium", 

457 title=title, 

458 description=description, 

459 attack_scenario=attack_scenario.strip(), 

460 file_path=context.get('file_path', 'unknown'), 

461 line_number=None, 

462 contract_name=context.get('contract_name', 'unknown'), 

463 economic_impact=economic_impact, 

464 remediation=remediation, 

465 confidence=75 # LLM-based confidence 

466 ) 

467 risks.append(risk) 

468 

469 except Exception as e: 

470 # Debug: LLM parsing failed 

471 pass 

472 

473 return risks 

474 

475 def _initialize_risk_patterns(self) -> Dict[str, List[str]]: 

476 """Initialize economic risk pattern detectors""" 

477 return { 

478 'flash_loan': [ 

479 r'flashloan', 

480 r'borrow.*flash', 

481 r'uniswap.*price', 

482 r'getAmountsOut' 

483 ], 

484 'oracle_manipulation': [ 

485 r'oracle', 

486 r'price.*feed', 

487 r'uniswap.*router', 

488 r'chainlink' 

489 ], 

490 'liquidity_drain': [ 

491 r'withdraw.*all', 

492 r'balance.*transfer', 

493 r'drain.*liquidity' 

494 ], 

495 'governance': [ 

496 r'vote.*balance', 

497 r'governance.*token', 

498 r'propose.*execute', 

499 r'timelock' 

500 ] 

501 } 

502 

503 def _initialize_llm_client(self): 

504 """Initialize LLM client - in production, this would connect to Claude/ChatGPT""" 

505 # For now, return None - LLM logic uses simulated responses 

506 return None