UNPKG

@ethereum-sourcify/bytecode-utils

Version:

Decode the CBOR encoded data at the end of an Ethereum contract's bytecode.

241 lines 17.5 kB
import { arrayify, hexlify } from '@ethersproject/bytes'; import bs58 from 'bs58'; import * as CBOR from 'cbor-x'; import semver from 'semver'; export var AuxdataStyle; (function (AuxdataStyle) { AuxdataStyle["SOLIDITY"] = "solidity"; AuxdataStyle["VYPER"] = "vyper"; AuxdataStyle["VYPER_LT_0_3_10"] = "vyper_lt_0_3_10"; AuxdataStyle["VYPER_LT_0_3_5"] = "vyper_lt_0_3_5"; })(AuxdataStyle || (AuxdataStyle = {})); /** * Decode contract's bytecode * @param bytecode - hex of the bytecode with 0x prefix * @param auxdataStyle - The style of auxdata, check AuxdataStyle enum for more info * @returns Object describing the contract */ export const decode = (bytecode, auxdataStyle) => { if (bytecode.length === 0) { throw Error('Bytecode cannot be null'); } if (bytecode.substring(0, 2) !== '0x') { bytecode = '0x' + bytecode; } // split auxdata const [, auxdata] = splitAuxdata(bytecode, auxdataStyle); if (!auxdata) { throw Error('Auxdata is not in the bytecode'); } // See more here: https://github.com/vyperlang/vyper/pull/3010 if (auxdataStyle === AuxdataStyle.VYPER) { // cbor decode the object and get a json const cborDecodedObject = CBOR.decode(arrayify(`0x${auxdata}`)); // Starting with version 0.3.10, Vyper stores the auxdata as an array // after 0.3.10: [runtimesize, datasize,immutablesize,version_cbor_object] // after 0.4.1: [integrity,runtimesize, datasize,immutablesize,version_cbor_object] // See more here: https://github.com/vyperlang/vyper/pull/3584 if (cborDecodedObject instanceof Array) { // read the last element from array, it contains the compiler version const compilerVersion = cborDecodedObject[cborDecodedObject.length - 1].vyper.join('.'); if (semver.gte(compilerVersion, '0.4.1')) { // Starting with version 0.4.1 Vyper added the integrity field // See more here: https://github.com/vyperlang/vyper/pull/4234 return { integrity: cborDecodedObject[0], runtimeSize: cborDecodedObject[1], dataSizes: cborDecodedObject[2], immutableSize: cborDecodedObject[3], vyperVersion: compilerVersion, }; } else if (semver.gte(compilerVersion, '0.3.10')) { return { runtimeSize: cborDecodedObject[0], dataSizes: cborDecodedObject[1], immutableSize: cborDecodedObject[2], vyperVersion: compilerVersion, }; } } throw Error('This version of Vyper is not supported'); } else if (auxdataStyle === AuxdataStyle.VYPER_LT_0_3_10 || auxdataStyle === AuxdataStyle.VYPER_LT_0_3_5) { // cbor decode the object and get a json const cborDecodedObject = CBOR.decode(arrayify(`0x${auxdata}`)); return { vyperVersion: cborDecodedObject.vyper.join('.'), }; } else if (auxdataStyle === AuxdataStyle.SOLIDITY) { // cbor decode the object and get a json const cborDecodedObject = CBOR.decode(arrayify(`0x${auxdata}`)); const result = {}; // Decode all the parameters from the json Object.keys(cborDecodedObject).forEach((key) => { switch (key) { case 'ipfs': { const ipfsCID = bs58.encode(cborDecodedObject.ipfs); result.ipfs = ipfsCID; break; } case 'solc': { // nightly builds are string encoded if (typeof cborDecodedObject.solc === 'string') { result.solcVersion = cborDecodedObject.solc; } else { result.solcVersion = cborDecodedObject.solc.join('.'); } break; } case 'experimental': { result.experimental = cborDecodedObject.experimental; break; } case 'bzzr0': case 'bzzr1': default: { result[key] = hexlify(cborDecodedObject[key]); break; } } }); return result; } else { throw Error('Invalid auxdata style'); } }; /** * Splits the bytecode into execution bytecode and auxdata. * If the bytecode does not contain CBOR-encoded auxdata, returns the whole bytecode. * * @param bytecode - Hex string of the bytecode with 0x prefix * @param auxdataStyle - The style of auxdata (Solidity or Vyper) * @returns An array containing execution bytecode and optionally auxdata and its length */ export const splitAuxdata = (bytecode, auxdataStyle) => { validateBytecode(bytecode); bytecode = ensureHexPrefix(bytecode); const bytesLength = 4; const cborBytesLength = getCborBytesLength(bytecode, auxdataStyle, bytesLength); if (isCborLengthInvalid(bytecode, cborBytesLength, bytesLength)) { return [bytecode]; } const auxdata = extractAuxdata(bytecode, auxdataStyle, cborBytesLength, bytesLength); const executionBytecode = extractExecutionBytecode(bytecode, cborBytesLength, bytesLength); if (isCborEncoded(auxdata)) { const cborLengthHex = getCborLengthHex(bytecode, auxdataStyle, bytesLength); return [executionBytecode, auxdata, cborLengthHex]; } return [bytecode]; }; /** * Validates that the bytecode is not empty. * * @param bytecode - The bytecode string to validate */ const validateBytecode = (bytecode) => { if (bytecode.length === 0) { throw Error('Bytecode cannot be null'); } }; /** * Ensures the bytecode string starts with '0x'. * * @param bytecode - The bytecode string * @returns The bytecode string with '0x' prefix */ const ensureHexPrefix = (bytecode) => { return bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`; }; /** * Determines the length of the CBOR auxdata in bytes. * * @param bytecode - The complete bytecode string * @param auxdataStyle - The style of auxdata * @param bytesLength - The length of bytes used to encode the CBOR length * @returns An object containing the CBOR bytes length and a flag for legacy Vyper */ const getCborBytesLength = (bytecode, auxdataStyle, bytesLength) => { if (auxdataStyle === AuxdataStyle.VYPER_LT_0_3_5) { return 22; } const cborLengthHex = bytecode.slice(-bytesLength); return parseInt(cborLengthHex, 16) * 2; }; /** * Checks if the CBOR length is invalid based on the bytecode length. * * @param bytecode - The complete bytecode string * @param cborBytesLength - The length of CBOR auxdata in bytes * @param bytesLength - The length of bytes used to encode the CBOR length * @returns True if the CBOR length is invalid, otherwise false */ const isCborLengthInvalid = (bytecode, cborBytesLength, bytesLength) => { return bytecode.length - bytesLength - cborBytesLength <= 0; }; /** * Extracts the auxdata from the bytecode based on the auxdata style. * * @param bytecode - The complete bytecode string * @param auxdataStyle - The style of auxdata * @param cborBytesLength - The length of CBOR auxdata in bytes * @param bytesLength - The length of bytes used to encode the CBOR length * @returns The extracted auxdata as a hex string */ const extractAuxdata = (bytecode, auxdataStyle, cborBytesLength, bytesLength) => { switch (auxdataStyle) { case AuxdataStyle.VYPER_LT_0_3_10: case AuxdataStyle.SOLIDITY: return bytecode.substring(bytecode.length - bytesLength - cborBytesLength, bytecode.length - bytesLength); case AuxdataStyle.VYPER: return bytecode.substring(bytecode.length - cborBytesLength, bytecode.length - bytesLength); case AuxdataStyle.VYPER_LT_0_3_5: return bytecode.substring(bytecode.length - 22, bytecode.length); default: throw Error('Unsupported auxdata style'); } }; /** * Extracts the execution bytecode from the complete bytecode string. * * @param bytecode - The complete bytecode string * @param cborBytesLength - The length of CBOR auxdata in bytes * @param bytesLength - The length of bytes used to encode the CBOR length * @returns The execution bytecode as a hex string */ const extractExecutionBytecode = (bytecode, cborBytesLength, bytesLength) => { return bytecode.substring(0, bytecode.length - bytesLength - cborBytesLength); }; /** * Attempts to decode the auxdata to verify if it's CBOR-encoded. * * @param auxdata - The auxdata string to decode * @returns True if auxdata is CBOR-encoded, otherwise false */ const isCborEncoded = (auxdata) => { try { CBOR.decode(arrayify(`0x${auxdata}`)); return true; } catch { return false; } }; /** * Retrieves the CBOR length from the bytecode based on the auxdata style. * * @param bytecode - The complete bytecode string * @param auxdataStyle - The style of auxdata * @param bytesLength - The length of bytes used to encode the CBOR length * @returns The CBOR length as a hex string */ const getCborLengthHex = (bytecode, auxdataStyle, bytesLength) => { if (auxdataStyle === AuxdataStyle.VYPER_LT_0_3_5) return ''; return bytecode.slice(-bytesLength); }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnl0ZWNvZGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbGliL2J5dGVjb2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDekQsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQ3hCLE9BQU8sS0FBSyxJQUFJLE1BQU0sUUFBUSxDQUFDO0FBQy9CLE9BQU8sTUFBTSxNQUFNLFFBQVEsQ0FBQztBQTBCNUIsTUFBTSxDQUFOLElBQVksWUFLWDtBQUxELFdBQVksWUFBWTtJQUN0QixxQ0FBcUIsQ0FBQTtJQUNyQiwrQkFBZSxDQUFBO0lBQ2YsbURBQW1DLENBQUE7SUFDbkMsaURBQWlDLENBQUE7QUFDbkMsQ0FBQyxFQUxXLFlBQVksS0FBWixZQUFZLFFBS3ZCO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsTUFBTSxNQUFNLEdBQUcsQ0FDcEIsUUFBZ0IsRUFDaEIsWUFBZSxFQUdNLEVBQUU7SUFDdkIsSUFBSSxRQUFRLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzFCLE1BQU0sS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7SUFDekMsQ0FBQztJQUNELElBQUksUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUM7UUFDdEMsUUFBUSxHQUFHLElBQUksR0FBRyxRQUFRLENBQUM7SUFDN0IsQ0FBQztJQUVELGdCQUFnQjtJQUNoQixNQUFNLENBQUMsRUFBRSxPQUFPLENBQUMsR0FBRyxZQUFZLENBQUMsUUFBUSxFQUFFLFlBQVksQ0FBQyxDQUFDO0lBQ3pELElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNiLE1BQU0sS0FBSyxDQUFDLGdDQUFnQyxDQUFDLENBQUM7SUFDaEQsQ0FBQztJQUVELDhEQUE4RDtJQUM5RCxJQUFJLFlBQVksS0FBSyxZ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