@nomiclabs/hardhat-etherscan
Version:
Hardhat plugin for verifying contracts on etherscan
124 lines (108 loc) • 4.78 kB
text/typescript
import debug from "debug";
import util from "util";
import { decodeFirstSync } from "cbor";
interface MetadataDescription {
solcVersion: string;
metadataSectionSizeInBytes: number;
}
export const METADATA_LENGTH_SIZE = 2;
export const METADATA_PRESENT_SOLC_NOT_FOUND_VERSION_RANGE = "0.4.7 - 0.5.8";
export const METADATA_ABSENT_VERSION_RANGE = "<0.4.7";
const log = debug("hardhat:hardhat-etherscan:metadata");
export function inferSolcVersion(bytecode: Buffer): MetadataDescription {
// We want to provide our best inference here.
// We can infer that some solidity compiler releases couldn't have produced this bytecode.
// Solc v0.4.7 was the first compiler to introduce metadata into the generated bytecode.
// See https://solidity.readthedocs.io/en/v0.4.7/miscellaneous.html#contract-metadata
// Solc v0.4.26, the last release for the v0.4 series, does not feature the compiler version in its emitted metadata.
// See https://solidity.readthedocs.io/en/v0.4.26/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
// Solc v0.5.9 was the first compiler to introduce its version into the metadata.
// See https://solidity.readthedocs.io/en/v0.5.9/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
// Solc v0.6.0 features compiler version metadata.
// See https://solidity.readthedocs.io/en/v0.6.0/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
let solcMetadata;
let metadataSectionSizeInBytes;
try {
const metadata = decodeSolcMetadata(bytecode);
log(`Metadata decoded: ${util.inspect(metadata.decoded)}`);
metadataSectionSizeInBytes = metadata.metadataSectionSizeInBytes;
solcMetadata = metadata.decoded.solc;
} catch {
// The decoding failed. Unfortunately, our only option is to assume that this bytecode was emitted by an old version.
// Technically, this bytecode could have been emitted by a compiler for another language altogether.
// TODO: add detection logic for other compilers if they become relevant?
log("Could not decode metadata.");
return {
metadataSectionSizeInBytes: 0,
solcVersion: METADATA_ABSENT_VERSION_RANGE,
};
}
if (solcMetadata instanceof Buffer) {
if (solcMetadata.length === 3) {
const [major, minor, patch] = solcMetadata;
const solcVersion = `${major}.${minor}.${patch}`;
log(`Solc version detected in bytecode: ${solcVersion}`);
return { metadataSectionSizeInBytes, solcVersion };
}
log(
`Found solc version field with ${solcMetadata.length} elements instead of three!`
);
}
// The embedded metadata was successfully decoded but there was no solc version in it.
log(`Could not detect solidity version in metadata.`);
return {
metadataSectionSizeInBytes,
solcVersion: METADATA_PRESENT_SOLC_NOT_FOUND_VERSION_RANGE,
};
}
export function decodeSolcMetadata(bytecode: Buffer) {
const metadataSectionLength = getSolcMetadataSectionLength(bytecode);
// The metadata and its length are in the last few bytes.
const metadataPayload = bytecode.slice(
-metadataSectionLength,
-METADATA_LENGTH_SIZE
);
log(`Read metadata length ${metadataSectionLength}`);
const lastMetadataBytes = metadataPayload.slice(-100);
log(
`Last ${
lastMetadataBytes.length
} bytes of metadata: ${lastMetadataBytes.toString("hex")}`
);
const decoded = decodeFirstSync(metadataPayload, { required: true });
return {
decoded,
metadataSectionSizeInBytes: metadataSectionLength,
};
}
export function getSolcMetadataSectionLength(bytecode: Buffer) {
return (
bytecode.slice(-METADATA_LENGTH_SIZE).readUInt16BE(0) + METADATA_LENGTH_SIZE
);
}
/**
* This function helps us measure the size of the executable section
* without actually decoding the whole bytecode string.
*
* This is useful because the runtime object emitted by the compiler
* may contain nonhexadecimal characters due to link placeholders.
*/
export function measureExecutableSectionLength(bytecode: string): number {
if (bytecode.startsWith("0x")) {
bytecode = bytecode.slice(2);
}
// `Buffer.from` will return a buffer that contains bytes up until the last decodable byte.
// To work around this we'll just slice the relevant part of the bytecode.
const metadataLengthSlice = Buffer.from(
bytecode.slice(-METADATA_LENGTH_SIZE * 2),
"hex"
);
// If, for whatever reason, the bytecode is so small that we can't even read two bytes off it,
// return the size of the entire bytecode.
if (metadataLengthSlice.length !== METADATA_LENGTH_SIZE) {
return bytecode.length;
}
const runtimeMetadataSectionLength =
getSolcMetadataSectionLength(metadataLengthSlice);
return bytecode.length - runtimeMetadataSectionLength * 2;
}