UNPKG

@nomiclabs/hardhat-etherscan

Version:
188 lines 9.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeBytecode = exports.compareBytecode = exports.extractMatchingContractInformation = exports.lookupMatchingBytecode = exports.Bytecode = void 0; const contract_names_1 = require("hardhat/utils/contract-names"); const metadata_1 = require("./metadata"); // If the compiler output bytecode is OVM bytecode, we need to make a fix to account for a bug in some versions of // the OVM compiler. The artifact’s deployedBytecode is incorrect, but because its bytecode (initcode) is correct, when we // actually deploy contracts, the code that ends up getting stored on chain is also correct. During verification, // Etherscan will compile the source code, pull out the artifact’s deployedBytecode, and then perform the // below find and replace, then check that resulting output against the code retrieved on chain from eth_getCode. // We define the strings for that find and replace here, and use them later so we can know if the bytecode matches // before it gets to Etherscan. Source: https://github.com/ethereum-optimism/optimism/blob/8d67991aba584c1703692ea46273ea8a1ef45f56/packages/contracts/src/contract-dumps.ts#L195-L204 const ovmFindOpcodes = "336000905af158601d01573d60011458600c01573d6000803e3d621234565260ea61109c52"; const ovmReplaceOpcodes = "336000905af158600e01573d6000803e3d6000fd5b3d6001141558600a015760016000f35b"; class Bytecode { constructor(bytecode) { this._bytecode = bytecode; const { solcVersion, metadataSectionSizeInBytes } = (0, metadata_1.inferSolcVersion)(Buffer.from(bytecode, "hex")); this._version = solcVersion; this._executableSection = { start: 0, length: bytecode.length - metadataSectionSizeInBytes * 2, }; this._metadataSection = { start: this._executableSection.length, length: metadataSectionSizeInBytes * 2, }; // Check if this is OVM bytecode by looking for the concatenation of the two opcodes defined here: // https://github.com/ethereum-optimism/optimism/blob/33cb9025f5e463525d6abe67c8457f81a87c5a24/packages/contracts/contracts/optimistic-ethereum/OVM/execution/OVM_SafetyChecker.sol#L143 // - This check would only fail if the EVM solidity compiler didn't use any of the following opcodes: https://github.com/ethereum-optimism/optimism/blob/c42fc0df2790a5319027393cb8fa34e4f7bb520f/packages/contracts/contracts/optimistic-ethereum/iOVM/execution/iOVM_ExecutionManager.sol#L94-L175 // This is the list of opcodes that calls the OVM execution manager. But the current solidity // compiler seems to add REVERT in all cases, meaning it currently won't happen and this check // will always be correct. // - It is possible, though very unlikely, that this string appears in the bytecode of an EVM // contract. As a result result, this _isOvm flag should only be used after trying to infer // the solc version // - We need this check because OVM bytecode has no metadata, so when verifying // OVM bytecode the check in `inferSolcVersion` will always return `METADATA_ABSENT_VERSION_RANGE`. this._isOvm = bytecode.includes(ovmReplaceOpcodes); } getInferredSolcVersion() { return this._version; } isOvmInferred() { return this._isOvm; } getExecutableSection() { const { start, length } = this._executableSection; return this._bytecode.slice(start, length); } hasMetadata() { return this._metadataSection.length > 0; } } exports.Bytecode = Bytecode; async function lookupMatchingBytecode(artifacts, matchingCompilerVersions, deployedBytecode) { const contractMatches = []; const fqNames = await artifacts.getAllFullyQualifiedNames(); for (const fqName of fqNames) { const buildInfo = await artifacts.getBuildInfo(fqName); if (buildInfo === undefined) { continue; } if (!matchingCompilerVersions.includes(buildInfo.solcVersion) && // if OVM, we will not have matching compiler versions because we can't infer a specific OVM solc version from the bytecode !deployedBytecode.isOvmInferred()) { continue; } const { sourceName, contractName } = (0, contract_names_1.parseFullyQualifiedName)(fqName); const contractInformation = await extractMatchingContractInformation(sourceName, contractName, buildInfo, deployedBytecode); if (contractInformation !== null) { contractMatches.push(contractInformation); } } return contractMatches; } exports.lookupMatchingBytecode = lookupMatchingBytecode; async function extractMatchingContractInformation(sourceName, contractName, buildInfo, deployedBytecode) { const contract = buildInfo.output.contracts[sourceName][contractName]; // Normalize deployed bytecode according to this contract. const { deployedBytecode: runtimeBytecodeSymbols } = contract.evm; // If this is OVM bytecode, do the required find and replace (see above comments for more info) if (deployedBytecode.isOvmInferred()) { runtimeBytecodeSymbols.object = runtimeBytecodeSymbols.object .split(ovmFindOpcodes) .join(ovmReplaceOpcodes); } const analyzedBytecode = await compareBytecode(deployedBytecode, runtimeBytecodeSymbols); if (analyzedBytecode !== null) { return { ...analyzedBytecode, compilerInput: buildInfo.input, compilerOutput: buildInfo.output, solcVersion: buildInfo.solcVersion, sourceName, contractName, contract, }; } return null; } exports.extractMatchingContractInformation = extractMatchingContractInformation; async function compareBytecode(deployedBytecode, runtimeBytecodeSymbols) { // We will ignore metadata information when comparing. Etherscan seems to do the same. const deployedExecutableSection = deployedBytecode.getExecutableSection(); const runtimeBytecodeExecutableSectionLength = (0, metadata_1.measureExecutableSectionLength)(runtimeBytecodeSymbols.object); if (deployedExecutableSection.length !== runtimeBytecodeExecutableSectionLength && // OVM bytecode has no metadata so we ignore this comparison if operating on OVM bytecode !deployedBytecode.isOvmInferred()) { return null; } // Normalize deployed bytecode according to this contract. const { immutableValues, libraryLinks, normalizedBytecode } = await normalizeBytecode(deployedExecutableSection, runtimeBytecodeSymbols); // Library hash placeholders are embedded into the bytes where the library addresses are linked. // We need to zero them out to compare them. const { normalizedBytecode: referenceBytecode } = await normalizeBytecode(runtimeBytecodeSymbols.object, runtimeBytecodeSymbols); if (normalizedBytecode.slice(0, deployedExecutableSection.length) === referenceBytecode.slice(0, deployedExecutableSection.length)) { // The bytecode matches return { immutableValues, libraryLinks, normalizedBytecode, }; } return null; } exports.compareBytecode = compareBytecode; async function normalizeBytecode(bytecode, symbols) { const nestedSliceReferences = []; const libraryLinks = {}; for (const [sourceName, libraries] of Object.entries(symbols.linkReferences)) { for (const [libraryName, linkReferences] of Object.entries(libraries)) { // Is this even a possibility? if (linkReferences.length === 0) { continue; } const { start, length } = linkReferences[0]; if (libraryLinks[sourceName] === undefined) { libraryLinks[sourceName] = {}; } // We have the bytecode encoded as a hex string libraryLinks[sourceName][libraryName] = `0x${bytecode.slice(start * 2, (start + length) * 2)}`; nestedSliceReferences.push(linkReferences); } } const immutableValues = {}; if (symbols.immutableReferences !== undefined && symbols.immutableReferences !== null) { for (const [key, immutableReferences] of Object.entries(symbols.immutableReferences)) { // Is this even a possibility? if (immutableReferences.length === 0) { continue; } const { start, length } = immutableReferences[0]; immutableValues[key] = bytecode.slice(start * 2, (start + length) * 2); nestedSliceReferences.push(immutableReferences); } } // To normalize a library object we need to take into account its call protection mechanism. // See https://solidity.readthedocs.io/en/latest/contracts.html#call-protection-for-libraries const addressSize = 20; const push20OpcodeHex = "73"; const pushPlaceholder = push20OpcodeHex + "0".repeat(addressSize * 2); if (symbols.object.startsWith(pushPlaceholder) && bytecode.startsWith(push20OpcodeHex)) { nestedSliceReferences.push([{ start: 1, length: addressSize }]); } const sliceReferences = flattenSlices(nestedSliceReferences); const normalizedBytecode = zeroOutSlices(bytecode, sliceReferences); return { libraryLinks, immutableValues, normalizedBytecode }; } exports.normalizeBytecode = normalizeBytecode; function flattenSlices(slices) { return [].concat(...slices); } function zeroOutSlices(code, slices) { for (const { start, length } of slices) { code = [ code.slice(0, start * 2), "0".repeat(length * 2), code.slice((start + length) * 2), ].join(""); } return code; } //# sourceMappingURL=bytecode.js.map