@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
JavaScript
/**
* 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;