@ethereum-sourcify/bytecode-utils
Version:
Decode the CBOR encoded data at the end of an Ethereum contract's bytecode.
241 lines • 17.5 kB
JavaScript
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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