// engine/solidity-auditor.ts
// SPEC-015b: Detect insecure patterns in Solidity code
// Reports vulnerabilities with SWC (Smart Contract Weakness Classification) references.
// Note: This is pattern-based detection, not bytecode analysis (no Slither/Mythril replacement).
import { readFile } from 'node:fs/promises';
import { glob } from 'glob';
import { join } from 'node:path';
// SWC reference: https://swcregistry.io/
const VULN_PATTERNS = [
    {
        pattern: /tx\.origin\s*(?:==|!=)/g,
        swcId: 'SWC-115',
        swcTitle: 'Authorization through tx.origin',
        severity: 'critical',
        description: 'Using tx.origin for authorization allows phishing attacks where a malicious contract can impersonate the original sender.',
        mitigation: 'Use msg.sender instead of tx.origin for authorization checks.',
    },
    {
        pattern: /block\.timestamp\s*(?:==|!=|<|>|%)/g,
        swcId: 'SWC-116',
        swcTitle: 'Block values as a proxy for time',
        severity: 'medium',
        description: 'block.timestamp can be manipulated by miners within ~15 seconds. Using it as a source of randomness or time-sensitive logic is unsafe.',
        mitigation: 'For randomness, use Chainlink VRF. For time-locks, accept minor miner manipulation or use block numbers.',
    },
    {
        pattern: /delegatecall\s*\(/g,
        swcId: 'SWC-112',
        swcTitle: 'Delegatecall to Untrusted Callee',
        severity: 'critical',
        description: 'delegatecall executes the callee code in the context of the caller, preserving storage. If target is untrusted, this allows storage manipulation and privilege escalation.',
        mitigation: 'Only delegatecall to trusted, immutable contracts. Validate target address. Consider using proxy patterns (EIP-1967) with upgrade controls.',
    },
    {
        pattern: /selfdestruct\s*\(/g,
        swcId: 'SWC-106',
        swcTitle: 'Unprotected SELFDESTRUCT Instruction',
        severity: 'critical',
        description: 'selfdestruct can permanently destroy the contract and send all ETH to a specified address. If unprotected, anyone can call it.',
        mitigation: 'Guard selfdestruct with onlyOwner modifier or time-lock. Consider removing self-destruct capability entirely in EIP-6049 compliant code.',
    },
    {
        pattern: /function\s+\w+\s*\([^)]*\)\s*(?:public|external)(?!\s+(?:view|pure|onlyOwner|nonReentrant|modifier))[^{]*\{[^}]*(?:\.call\{value:[^}]*\}|\.transfer\(|\.send\()[^}]*\}/gs,
        swcId: 'SWC-107',
        swcTitle: 'Reentrancy',
        severity: 'critical',
        description: 'External calls before state updates allow reentrant calls that can drain funds or corrupt state.',
        mitigation: 'Apply checks-effects-interactions pattern: update state BEFORE external calls. Use OpenZeppelin ReentrancyGuard (nonReentrant modifier).',
    },
];
// Separate check for missing ReentrancyGuard (SWC-107 detection via absence)
const MISSING_REENTRANCY_GUARD_PATTERN = /import.*ReentrancyGuard|nonReentrant/;
async function readSolidityFiles(projectPath) {
    let solFiles;
    try {
        solFiles = await glob('**/*.sol', {
            cwd: projectPath,
            absolute: true,
            ignore: ['**/node_modules/**', '**/lib/**', '**/out/**', '**/artifacts/**'],
        });
    }
    catch {
        /* v8 ignore next */
        return [];
    }
    const results = [];
    for (const file of solFiles) {
        try {
            const content = await readFile(file, 'utf-8');
            results.push({ file, content });
        }
        catch {
            // skip unreadable files
        }
    }
    return results;
}
function scanFileForVulnerabilities(file, content) {
    const findings = [];
    const lines = content.split('\n');
    for (const vulnPattern of VULN_PATTERNS) {
        // Skip the reentrancy pattern (too complex for line-by-line) — use dedicated check
        if (vulnPattern.swcId === 'SWC-107') {
            continue;
        }
        vulnPattern.pattern.lastIndex = 0;
        let match;
        while ((match = vulnPattern.pattern.exec(content)) !== null) {
            // Find line number for the match
            const upToMatch = content.slice(0, match.index);
            const lineNumber = upToMatch.split('\n').length;
            // Check if this is inside a comment
            const lineContent = lines[lineNumber - 1] ?? '';
            if (lineContent.trimStart().startsWith('//') || lineContent.trimStart().startsWith('*')) {
                continue;
            }
            findings.push({
                pattern: match[0].trim(),
                swcId: vulnPattern.swcId,
                swcTitle: vulnPattern.swcTitle,
                severity: vulnPattern.severity,
                description: vulnPattern.description,
                mitigation: vulnPattern.mitigation,
                file: file.replace(join(file, '..', '..'), ''), // relative path
                line: lineNumber,
            });
        }
    }
    // Check for missing ReentrancyGuard in contracts that make external calls
    const hasExternalCall = content.includes('.call{value:') ||
        content.includes('.transfer(') ||
        content.includes('.send(');
    const hasReentrancyGuard = MISSING_REENTRANCY_GUARD_PATTERN.test(content);
    if (hasExternalCall && !hasReentrancyGuard) {
        findings.push({
            pattern: 'Missing ReentrancyGuard',
            swcId: 'SWC-107',
            swcTitle: 'Reentrancy',
            severity: 'critical',
            description: 'Contract makes external calls (call/transfer/send) without ReentrancyGuard. ' +
                'This may allow reentrant attacks that drain funds or corrupt state.',
            mitigation: 'Import OpenZeppelin ReentrancyGuard and add nonReentrant modifier to state-changing functions that make external calls.',
            file,
        });
    }
    return findings;
}
/**
 * Audit a Solidity project for common security vulnerabilities.
 * Detects anti-patterns and reports with SWC references.
 */
export async function auditSolidityProject(projectPath) {
    const solidityFiles = await readSolidityFiles(projectPath);
    if (solidityFiles.length === 0) {
        return {
            vulnerabilities: [],
            auditedFiles: 0,
            criticalCount: 0,
            highCount: 0,
            mediumCount: 0,
            lowCount: 0,
            summary: 'No Solidity files found in project path.',
        };
    }
    const allVulnerabilities = [];
    for (const { file, content } of solidityFiles) {
        const findings = scanFileForVulnerabilities(file, content);
        allVulnerabilities.push(...findings);
    }
    const criticalCount = allVulnerabilities.filter((v) => v.severity === 'critical').length;
    const highCount = allVulnerabilities.filter((v) => v.severity === 'high').length;
    const mediumCount = allVulnerabilities.filter((v) => v.severity === 'medium').length;
    const lowCount = allVulnerabilities.filter((v) => v.severity === 'low').length;
    const summaryParts = [];
    if (criticalCount > 0) {
        summaryParts.push(`${criticalCount} critical`);
    }
    /* v8 ignore next 2 */
    if (highCount > 0) {
        summaryParts.push(`${highCount} high`);
    }
    if (mediumCount > 0) {
        summaryParts.push(`${mediumCount} medium`);
    }
    /* v8 ignore next 2 */
    if (lowCount > 0) {
        summaryParts.push(`${lowCount} low`);
    }
    const summary = summaryParts.length > 0
        ? `Solidity audit found ${allVulnerabilities.length} vulnerabilities: ${summaryParts.join(', ')}.`
        : `Solidity audit completed. No known vulnerability patterns detected in ${solidityFiles.length} file(s).`;
    return {
        vulnerabilities: allVulnerabilities,
        auditedFiles: solidityFiles.length,
        criticalCount,
        highCount,
        mediumCount,
        lowCount,
        summary,
    };
}
//# sourceMappingURL=solidity-auditor.js.map