UNPKG

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