#!/usr/bin/env python3 """#19 — Smart Contract Function Analyzer. Extends SENTINEL to decompile and analyze bytecode. Returns human-readable "what this contract actually does." """ import os import re from typing import Any import httpx from fastapi import APIRouter, Query router = APIRouter(prefix="/api/v1/contract-analyzer", tags=["contract-analyzer"]) BACKEND = os.environ.get("BACKEND_URL", "http://localhost:8000") # Known suspicious function signatures (4-byte selectors) SUSPICIOUS_SELECTORS = { "0x18160ddd": "totalSupply()", "0x06fdde03": "name()", "0x95d89b41": "symbol()", "0x313ce567": "decimals()", "0x70a08231": "balanceOf(address)", "0xa9059cbb": "transfer(address,uint256)", "0x23b872dd": "transferFrom(address,address,uint256)", "0x095ea7b3": "approve(address,uint256)", "0x42966c68": "burn(uint256)", "0x79cc6790": "burnFrom(address,uint256)", "0xf2fde38b": "transferOwnership(address)", "0x8da5cb5b": "owner()", "0x8456cb59": "pause()", "0x3f4ba83a": "unpause()", "0x40c10f19": "mint(address,uint256)", "0x9dc29fac": "burn(uint256)", "0x79ba5097": "renounceOwnership()", "0x715018a6": "renounceOwnership()", "0x83197ef0": "includeInReward(address)", "0xea8a1af0": "excludeFromReward(address)", "0x7f41468d": "setSwapAndLiquifyEnabled(bool)", "0x55505aac": "setMaxTxPercent(uint256)", "0x3141de2d": "setMaxWalletPercent(uint256)", } # Patterns that indicate malicious intent MALICIOUS_PATTERNS = [ ("unlimited mint", [r"mint\(", r"_mint\(", r"mint_without_limit"]), ("hidden owner functions", [r"onlyOwner", r"require\(.*owner"]), ("transfer fee manipulation", [r"setTax", r"setFee", r"updateFee"]), ("blacklist capability", [r"blacklist", r"isBlacklisted", r"_blacklist"]), ("pause/trading halt", [r"pause\(", r"unpause\(", r"setTrading"]), ("reentrancy risk", [r"\.call\{", r"\.transfer\("]), ("selfdestruct", [r"selfdestruct\(", r"suicide\("]), ("proxy upgrade risk", [r"upgradeTo\(", r"upgradeToAndCall\(", r"_upgradeTo"]), ] async def _get_contract_bytecode(address: str, chain: str) -> dict[str, Any]: """Fetch contract bytecode and source from Etherscan/Blockscout.""" explorers = { "ethereum": "https://api.etherscan.io/api", "bsc": "https://api.bscscan.com/api", "polygon": "https://api.polygonscan.com/api", "arbitrum": "https://api.arbiscan.io/api", "base": "https://api.basescan.org/api", "optimism": "https://api-optimistic.etherscan.io/api", } result = {"bytecode": "", "source": "", "abi": [], "verified": False} api_base = explorers.get(chain) if not api_base: return result try: async with httpx.AsyncClient(timeout=10) as client: # Get bytecode resp = await client.get( f"{api_base}", params={"module": "proxy", "action": "eth_getCode", "address": address, "apikey": "YourApiKeyToken"}, ) if resp.status_code == 200: result["bytecode"] = resp.json().get("result", "") # Check if verified resp2 = await client.get( f"{api_base}", params={ "module": "contract", "action": "getsourcecode", "address": address, "apikey": "YourApiKeyToken", }, ) if resp2.status_code == 200: data = resp2.json().get("result", [{}])[0] result["source"] = data.get("SourceCode", "") result["abi"] = data.get("ABI", "[]") result["verified"] = True except Exception: pass return result def _analyze_bytecode(bytecode: str, source: str) -> dict: """Analyze contract bytecode/source for suspicious patterns.""" findings: list[dict] = [] risk_score = 0 # Check for suspicious function selectors in bytecode for selector, name in SUSPICIOUS_SELECTORS.items(): if selector[2:] in bytecode.lower(): risk_map = { "mint": 15, "burn": 5, "pause": 10, "transferOwnership": 10, "renounceOwnership": 5, "includeInReward": 10, "setSwapAndLiquifyEnabled": 10, "setMaxTxPercent": 10, "setMaxWalletPercent": 10, } for key, score in risk_map.items(): if key in name: risk_score += score findings.append( { "function": name, "selector": selector, "risk": "HIGH" if score >= 10 else "MEDIUM", "explanation": f"Contract has {name} function — {'can create unlimited tokens' if 'mint' in name.lower() else 'may be used to manipulate trading'}", } ) break # Check source code for malicious patterns if source: for pattern_name, regexes in MALICIOUS_PATTERNS: for regex in regexes: if re.search(regex, source, re.IGNORECASE): risk_score += 10 findings.append( { "pattern": pattern_name, "matched": regex, "risk": "HIGH", "explanation": f"Source code contains {pattern_name} pattern", } ) break risk_level = ( "CRITICAL" if risk_score >= 50 else "HIGH" if risk_score >= 25 else "MEDIUM" if risk_score >= 10 else "LOW" ) return {"risk_score": min(100, risk_score), "risk_level": risk_level, "findings": findings} @router.get("/analyze/{address}") async def analyze_contract(address: str, chain: str = Query("ethereum")): """Analyze a smart contract's functions and risk profile.""" info = await _get_contract_bytecode(address, chain) analysis = _analyze_bytecode(info.get("bytecode", ""), info.get("source", "")) # Also run SENTINEL scan try: async with httpx.AsyncClient(timeout=10) as client: resp = await client.post( f"{BACKEND}/api/v1/token/scan", json={"token_address": address, "chain": chain}, headers={"X-RMI-Key": "rmi-internal-2026"}, ) if resp.status_code == 200: sentinel = resp.json() analysis["sentinel_score"] = sentinel.get("safety_score", 50) analysis["sentinel_flags"] = sentinel.get("risk_flags", []) except Exception: pass return { "contract": address, "chain": chain, "verified": info.get("verified", False), "bytecode_length": len(info.get("bytecode", "")), "analysis": analysis, "summary": ( f"Risk: {analysis['risk_level']} ({analysis['risk_score']}/100). " f"{len(analysis['findings'])} suspicious patterns found. " f"Verified: {'Yes' if info.get('verified') else 'No'}." ), } @router.get("/detect-mint/{address}") async def detect_mint_function(address: str, chain: str = Query("ethereum")): """Quick check: does this contract have a mint function?""" info = await _get_contract_bytecode(address, chain) bytecode = info.get("bytecode", "") mint_selectors = ["40c10f19", "079cc6790", "a0712d68"] found = [] for sel in mint_selectors: if sel in bytecode.lower(): name = SUSPICIOUS_SELECTORS.get(f"0x{sel}", "unknown mint function") found.append({"selector": f"0x{sel}", "function": name}) return { "contract": address, "has_mint_function": len(found) > 0, "mint_functions": found, "risk": "HIGH" if len(found) > 0 else "LOW", "note": "Mint functions allow creating new tokens — typical of scams" if found else "No mint function detected", }