@bcoders.gr/evm-disassembler
Version:
A comprehensive EVM bytecode disassembler and analyzer with support for multiple EVM versions
201 lines (175 loc) • 6.73 kB
JavaScript
/**
* Metadata detection and parsing for EVM bytecode
* @module metadata
*/
const { MetadataParsingError } = require('./errors');
const { METADATA_MARKERS } = require('./constants');
/**
* Detect contract metadata in bytecode with enhanced accuracy
* @param {string} bytecode - Hex string of bytecode
* @returns {Object} Metadata info or null if not found
*/
function detectMetadata(bytecode) {
// Look for common CBOR-encoded metadata patterns
for (const marker of METADATA_MARKERS) {
const index = bytecode.indexOf(marker.pattern);
if (index === -1) continue;
return extractMetadataInfo(bytecode, index, marker);
}
return null;
}
/**
* Performance-optimized metadata detection using Map for faster lookups
* @param {string} bytecode - Hex string of bytecode
* @returns {Object} Metadata info or null if not found
*/
function detectMetadataOptimized(bytecode) {
// Use Map for O(1) lookups instead of array iteration
const markerMap = new Map(
METADATA_MARKERS.map(marker => [marker.pattern, marker])
);
// Find all potential metadata markers efficiently
const candidates = [];
for (const [pattern, marker] of markerMap) {
let index = bytecode.indexOf(pattern);
while (index !== -1) {
candidates.push({ index, marker });
index = bytecode.indexOf(pattern, index + 1);
}
}
if (candidates.length === 0) return null;
// Sort by position to find the earliest valid metadata
candidates.sort((a, b) => a.index - b.index);
// Return the first valid metadata found
for (const candidate of candidates) {
try {
const metadata = extractMetadataInfo(bytecode, candidate.index, candidate.marker);
if (metadata && !metadata.error) {
return metadata;
}
} catch (error) {
// Continue to next candidate if this one fails
continue;
}
}
return null;
}
/**
* Extract metadata information from bytecode
* @param {string} bytecode - Hex string of bytecode
* @param {number} index - Index where metadata marker was found
* @param {Object} marker - Metadata marker object
* @returns {Object} Metadata information
*/
function extractMetadataInfo(bytecode, index, marker) {
// Found metadata marker
const metadataStart = Math.max(0, index - 4); // Adjust for potential PUSH before marker
const metadataHex = bytecode.substring(metadataStart);
try {
// Try to extract compiler and version info
let version = '';
let compilerType = marker.type;
// For Solidity, try to extract version
if (marker.type === 'solc') {
const versionMatch = metadataHex.match(/64736f6c6343([0-9a-f]+)/i);
if (versionMatch) {
const hex = versionMatch[1];
// Convert hex to version string (e.g. "080a" → "0.8.10")
version = hex.match(/.{1,2}/g)
.map(h => parseInt(h, 16).toString())
.join('.');
}
}
// Try to determine exact metadata length
let metadataLength = bytecode.length - metadataStart;
// Look for CBOR encoded data pattern endings
const cborEndingMatch = metadataHex.match(/a264([0-9a-f]{2,6})$/i);
if (cborEndingMatch) {
metadataLength = metadataHex.indexOf(cborEndingMatch[0]) + cborEndingMatch[0].length;
}
return {
startIndex: metadataStart,
startByte: metadataStart / 2,
endByte: (metadataStart + metadataLength) / 2,
length: metadataLength / 2,
metadata: metadataHex.substring(0, metadataLength),
compiler: marker.name,
compilerType,
version: version || 'unknown'
};
} catch (e) {
return {
startIndex: metadataStart,
startByte: metadataStart / 2,
metadata: metadataHex,
error: e.message
};
}
}
/**
* Smart detection to check if a PUSH instruction appears to be part of contract metadata
* @param {Object} instruction - The instruction object to check
* @param {number} metadataBoundary - The start position of detected metadata
* @param {number} bytePos - Current byte position in bytecode
* @returns {boolean} True if it should be skipped as metadata
*/
function isLikelyMetadata(instruction, metadataBoundary, bytePos) {
// If we're past the detected metadata boundary
if (metadataBoundary && bytePos >= metadataBoundary / 2) {
return true;
}
// If we're near the end of the contract and seeing suspicious PUSH data
if (instruction.pushData) {
// Check for common metadata patterns in push data
const metadataPatterns = [
/a164736f6c6343/i, // Solidity metadata marker
/a265627a7a72315820/i, // Older Solidity metadata
/ipfs/i, // IPFS links in metadata
/64736f6c63/i, // "solc" in hex
/b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850/i, // Vyper marker
];
for (const pattern of metadataPatterns) {
if (pattern.test(instruction.pushData)) {
return true;
}
}
}
return false;
}
/**
* Parse CBOR-encoded metadata
* @param {string} metadataHex - Hex string of metadata
* @returns {Object} Parsed metadata object
*/
function parseCBORMetadata(metadataHex) {
// Basic CBOR parsing for common metadata fields
const metadata = {};
try {
// Look for IPFS hash
const ipfsMatch = metadataHex.match(/69706673([0-9a-f]{2})([0-9a-f]+)/i);
if (ipfsMatch) {
const length = parseInt(ipfsMatch[1], 16);
metadata.ipfs = ipfsMatch[2].substring(0, length * 2);
}
// Look for Swarm hash
const swarmMatch = metadataHex.match(/627a7a72([0-9a-f]{2})([0-9a-f]+)/i);
if (swarmMatch) {
const length = parseInt(swarmMatch[1], 16);
metadata.swarm = swarmMatch[2].substring(0, length * 2);
}
// Look for experimental flag
if (metadataHex.includes('6578706572696d656e74616c')) {
metadata.experimental = true;
}
return metadata;
} catch (error) {
throw new MetadataParsingError(`Failed to parse CBOR metadata: ${error.message}`, metadataHex);
}
}
module.exports = {
detectMetadata,
detectMetadataOptimized,
extractMetadataInfo,
isLikelyMetadata,
parseCBORMetadata
};