UNPKG

@bcoders.gr/evm-disassembler

Version:

A comprehensive EVM bytecode disassembler and analyzer with support for multiple EVM versions

408 lines (362 loc) 12.9 kB
/** * EVM Bytecode Disassembler * A comprehensive tool for analyzing Ethereum Virtual Machine bytecode * * @module evm-disassembler */ // Core modules const { decodeBytecode, validateBytecode, normalizeBytecode, extractERC20Data, isERC20Contract, extractFunctions, extractVariables, extractMappings, extractContractName } = require('./lib/decoder'); const { analyzeBytecode, generateSummary } = require('./lib/analyzer'); const { formatAsText, formatAsJSON, formatAsAssembly, formatAsMarkdown, formatAsCSV } = require('./lib/formatter'); // Utility modules const { detectMetadata } = require('./lib/metadata'); const { detectFunctions } = require('./lib/function-detector'); const { analyzeStackDepth } = require('./lib/stack-analyzer'); // Constants and errors const { BytecodeDecoderError, InvalidBytecodeError } = require('./lib/errors'); const { KNOWN_SIGNATURES } = require('./lib/constants'); const { DEFAULT_OPCODES, EVM_VERSIONS, getOpcodesForVersion } = require('./lib/opcodes'); /** * Main disassembler class */ class EVMDisassembler { constructor(options = {}) { this.options = { evmVersion: 'latest', includeMetadata: true, stopAtMetadata: true, performStackAnalysis: true, performFunctionAnalysis: true, performSecurityAnalysis: true, ...options }; } /** * Disassemble bytecode with full analysis * @param {string} bytecode - Hex string of bytecode * @returns {Object} Complete disassembly results */ disassemble(bytecode) { try { // Normalize bytecode bytecode = normalizeBytecode(bytecode); // Perform analysis const analysis = analyzeBytecode(bytecode, this.options); if (!analysis.valid) { throw new InvalidBytecodeError(analysis.error); } // Generate summary analysis.summary = generateSummary(analysis); return analysis; } catch (error) { if (error instanceof BytecodeDecoderError) { throw error; } throw new BytecodeDecoderError(`Disassembly failed: ${error.message}`); } } /** * Decode bytecode without analysis * @param {string} bytecode - Hex string of bytecode * @returns {Array} Decoded instructions */ decode(bytecode) { return decodeBytecode(bytecode, this.options); } /** * Format disassembly results * @param {Object} results - Disassembly results * @param {string} format - Output format (text, json, assembly, markdown, csv) * @param {Object} options - Format-specific options * @returns {string} Formatted output */ format(results, format = 'text', options = {}) { const instructions = results.instructions || results; switch (format.toLowerCase()) { case 'text': return formatAsText(instructions, options); case 'json': return formatAsJSON(instructions, results, options); case 'assembly': case 'asm': return formatAsAssembly(instructions, options); case 'markdown': case 'md': return formatAsMarkdown(instructions, results, options); case 'csv': return formatAsCSV(instructions); default: throw new Error(`Unknown format: ${format}`); } } /** * Quick validation of bytecode * @param {string} bytecode - Hex string of bytecode * @returns {Object} Validation result */ validate(bytecode) { return validateBytecode(bytecode); } /** * Detect functions in bytecode * @param {string} bytecode - Hex string of bytecode * @returns {Object} Function detection results */ detectFunctions(bytecode) { const instructions = this.decode(bytecode); return detectFunctions(instructions); } /** * Analyze stack depth * @param {string} bytecode - Hex string of bytecode * @returns {Object} Stack analysis results */ analyzeStack(bytecode) { const instructions = this.decode(bytecode); return analyzeStackDepth(instructions); } /** * Get opcodes for specific EVM version * @param {string} version - EVM version * @returns {Object} Opcodes map */ getOpcodes(version = 'latest') { return getOpcodesForVersion(version); } /** * Compare function patterns in bytecode * @param {string} bytecode - Hex string of bytecode * @returns {Object} Pattern comparison results */ compareFunctionPatterns(bytecode) { const instructions = this.decode(bytecode); const functions = detectFunctions(instructions); const { compareFunctionPatterns } = require('./lib/function-detector'); return compareFunctionPatterns(functions.functions); } /** * Get detailed function analysis with opcode patterns * @param {string} bytecode - Hex string of bytecode * @returns {Object} Detailed function analysis including patterns */ analyzeFunctionsWithPatterns(bytecode) { const instructions = this.decode(bytecode); const functions = detectFunctions(instructions); const { compareFunctionPatterns } = require('./lib/function-detector'); const patternComparisons = compareFunctionPatterns(functions.functions); return { ...functions, patternComparisons, functionsWithPatterns: functions.functions.map(func => ({ selector: func.selector, signature: func.signature, opcodePattern: func.opcodePattern, patternHash: func.patternHash, instructionCount: func.instructionCount, stackOperations: func.stackOperations, storageOperations: func.storageOperations, memoryOperations: func.memoryOperations, controlFlow: func.controlFlow })) }; } /** * Extract ERC20 contract data from Solidity source code * @param {string} sourceCode - Solidity source code * @returns {Object} Extracted contract data */ extractERC20Data(sourceCode) { return extractERC20Data(sourceCode); } /** * Check if source code is an ERC20 contract * @param {string} sourceCode - Solidity source code * @returns {boolean} True if ERC20 contract */ isERC20Contract(sourceCode) { return isERC20Contract(sourceCode); } /** * Extract functions from Solidity source code * @param {string} sourceCode - Solidity source code * @returns {Array} Array of extracted functions */ extractSourceFunctions(sourceCode) { return extractFunctions(sourceCode); } /** * Extract variables from Solidity source code * @param {string} sourceCode - Solidity source code * @returns {Array} Array of extracted variables */ extractSourceVariables(sourceCode) { return extractVariables(sourceCode); } /** * Extract mappings from Solidity source code * @param {string} sourceCode - Solidity source code * @returns {Array} Array of extracted mappings */ extractSourceMappings(sourceCode) { return extractMappings(sourceCode); } /** * Extract contract name from Solidity source code * @param {string} sourceCode - Solidity source code * @returns {string} Contract name */ extractContractName(sourceCode) { return extractContractName(sourceCode); } /** * Comprehensive analysis combining bytecode disassembly with source code extraction * @param {string} bytecode - Hex string of bytecode * @param {string} sourceCode - Solidity source code (optional) * @returns {Object} Combined analysis results */ analyzeWithSource(bytecode, sourceCode = null) { const bytecodeAnalysis = this.disassemble(bytecode); if (sourceCode && typeof sourceCode === 'string') { try { const sourceAnalysis = extractERC20Data(sourceCode); return { ...bytecodeAnalysis, sourceAnalysis, combined: { hasSourceCode: true, isERC20: !sourceAnalysis.error, sourceMatchesBytecode: this.compareSourceToBytecode(sourceAnalysis, bytecodeAnalysis) } }; } catch (error) { return { ...bytecodeAnalysis, sourceAnalysis: { error: `Source analysis failed: ${error.message}` }, combined: { hasSourceCode: false, isERC20: false, sourceMatchesBytecode: false } }; } } return { ...bytecodeAnalysis, combined: { hasSourceCode: false, isERC20: false, sourceMatchesBytecode: false } }; } /** * Compare source code functions with bytecode functions * @param {Object} sourceAnalysis - Source code analysis * @param {Object} bytecodeAnalysis - Bytecode analysis * @returns {Object} Comparison results */ compareSourceToBytecode(sourceAnalysis, bytecodeAnalysis) { if (!sourceAnalysis.functions || !bytecodeAnalysis.functions) { return { match: false, reason: 'Missing function data' }; } const sourceFunctions = sourceAnalysis.functions.map(f => f.name.toLowerCase()); const bytecodeFunctions = bytecodeAnalysis.functions.functions .filter(f => f.isKnown) .map(f => { // Extract function name from signature const match = f.signature.match(/^(\w+)\(/); return match ? match[1].toLowerCase() : null; }) .filter(name => name !== null); const commonFunctions = sourceFunctions.filter(name => bytecodeFunctions.includes(name) ); const matchPercentage = sourceFunctions.length > 0 ? (commonFunctions.length / sourceFunctions.length) * 100 : 0; return { match: matchPercentage > 70, // Consider it a match if 70%+ functions are found matchPercentage: Math.round(matchPercentage), commonFunctions, sourceFunctionCount: sourceFunctions.length, bytecodeFunctionCount: bytecodeFunctions.length, missingInBytecode: sourceFunctions.filter(name => !bytecodeFunctions.includes(name)), extraInBytecode: bytecodeFunctions.filter(name => !sourceFunctions.includes(name)) }; } } /** * Convenience function for quick disassembly * @param {string} bytecode - Hex string of bytecode * @param {Object} options - Disassembly options * @returns {Object} Disassembly results */ function disassemble(bytecode, options = {}) { const disassembler = new EVMDisassembler(options); return disassembler.disassemble(bytecode); } /** * Convenience function for quick decoding * @param {string} bytecode - Hex string of bytecode * @param {Object} options - Decoding options * @returns {Array} Decoded instructions */ function decode(bytecode, options = {}) { return decodeBytecode(bytecode, options); } // Export main class and convenience functions module.exports = { // Main class EVMDisassembler, // Convenience functions disassemble, decode, // Core functions decodeBytecode, analyzeBytecode, validateBytecode, normalizeBytecode, // Formatting functions formatAsText, formatAsJSON, formatAsAssembly, formatAsMarkdown, formatAsCSV, // Analysis functions detectMetadata, detectFunctions, analyzeStackDepth, generateSummary, // Utilities getOpcodesForVersion, // Constants KNOWN_SIGNATURES, DEFAULT_OPCODES, EVM_VERSIONS, // Errors BytecodeDecoderError, InvalidBytecodeError }; // Also export as default module.exports.default = EVMDisassembler;