@dydxfoundation/governance
Version:
dYdX governance smart contracts
111 lines (110 loc) • 5.93 kB
JavaScript
;
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable global-require */
/* eslint-disable no-unexpected-multiline */
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyContract = void 0;
const path_1 = __importDefault(require("path"));
const ethers_1 = require("ethers");
const lodash_1 = __importDefault(require("lodash"));
const hre_1 = require("../hre");
const hex_1 = require("./hex");
const logging_1 = require("./logging");
const ARTIFACTS_DIR = '../../artifacts';
async function verifyContract(contractPath, contractName, deployedAddress, immutableValues = {}) {
const hre = (0, hre_1.getHre)();
// Load-in compiled JSON files.
const solidityPath = path_1.default.join(contractPath, `${contractName}.sol`);
const contractJson = require(path_1.default.join(ARTIFACTS_DIR, `${solidityPath}/${contractName}.json`));
const dbgJson = require(path_1.default.join(ARTIFACTS_DIR, `${solidityPath}/${contractName}.dbg.json`));
const buildInfoJson = require(path_1.default.join(ARTIFACTS_DIR, `build-info/${dbgJson.buildInfo.split('/').pop()}`));
// Get the deployed and expected bytecodes.
const deployedBytecodeString = await hre.ethers.provider.getCode(deployedAddress);
const deployedBytecode = Buffer.from((0, hex_1.stripHexPrefix)(deployedBytecodeString), 'hex');
const expectedBytecode = Buffer.from((0, hex_1.stripHexPrefix)(contractJson.deployedBytecode), 'hex');
// Find information for immutable values in contract.
const immutableReferences = buildInfoJson
.output
.contracts[solidityPath][contractName]
.evm
.deployedBytecode
.immutableReferences;
// For each immutable variable in the contract...
Object.keys(immutableReferences).forEach((immutableId) => {
var _a;
// Find the key associated with this immutableId.
// Find an object, `o`, at `sources.*.ast.nodes[*].nodes[*]` where `o.id == immutableId`.
// Return `o.name` which is the name of the immutable variable in solidity.
const immutableKey = (_a = lodash_1.default.chain(buildInfoJson.output.sources)
.values()
.map('ast.nodes')
.filter() // remove any without `ast.nodes`
.flatten()
.map('nodes')
.filter()
.flatten()
.find((o) => (o.mutability === 'immutable' &&
typeof o.id === 'number' &&
o.id.toString() === immutableId))
.value()) === null || _a === void 0 ? void 0 : _a.name;
if (!immutableKey) {
throw new Error(`cannot find key for immutableId: ${immutableId}`);
}
// Get expected value of the immutable.
const immutableValue = immutableValues[immutableKey];
if (!immutableValue) {
throw new Error(`did not pass-in value for immutableKey: ${immutableKey}`);
}
// Place the value of the immutable in the expectedBytecode.
const bytecodeLocations = immutableReferences[immutableId];
bytecodeLocations.forEach((loc) => {
const expectedValueHex = ethers_1.utils.hexValue(immutableValue);
const expectedValueHexPadded = ethers_1.utils.hexZeroPad(expectedValueHex, loc.length);
const stringifiedValue = Buffer.from((0, hex_1.stripHexPrefix)(expectedValueHexPadded), 'hex');
for (let i = 0; i < loc.length; i += 1) {
expectedBytecode[loc.start + i] = stringifiedValue[i];
}
});
});
(0, logging_1.log)(`Using network: ${(await hre.ethers.provider.getNetwork()).chainId}\n`);
(0, logging_1.log)(`Checking bytecode deployed at ${deployedAddress} against ` +
`the locally compiled bytecode for contract '${solidityPath}'.`);
if (deployedBytecode.length !== expectedBytecode.length) {
throw new Error(`Deployed bytecode length: ${deployedBytecode.length}, ` +
`Expected bytecode length: ${expectedBytecode.length}`);
}
const deployedMetadata = getMetadata(deployedBytecode);
const expectedMetadata = getMetadata(expectedBytecode);
if (deployedMetadata.length !== expectedMetadata.length) {
throw new Error(`Deployed bytecode metadata length: ${deployedMetadata.length}, ` +
`Expected bytecode metadata length: ${expectedMetadata.length}`);
}
const metadataLength = deployedMetadata.length;
const codeLength = deployedBytecode.length - metadataLength;
const metadataMatch = deployedMetadata.equals(expectedMetadata);
const deployedCode = deployedBytecode.slice(0, codeLength);
const expectedCode = expectedBytecode.slice(0, codeLength);
const bytecodeMatch = deployedCode.equals(expectedCode);
(0, logging_1.log)('\n');
(0, logging_1.log)(`Metadata length: ${metadataLength} bytes`);
(0, logging_1.log)(`Bytecode length (non-metadata): ${codeLength} bytes`);
(0, logging_1.log)(`Metadata match: ${metadataMatch}`);
(0, logging_1.log)(`Bytecode match: ${bytecodeMatch}`);
// Do not throw error on Metadata mismatch since it may be different depending on the local filesystem.
// https://docs.soliditylang.org/en/develop/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
// https://github.com/trufflesuite/truffle-compile/issues/77
if (!bytecodeMatch) {
throw new Error('Bytecode mismatch.');
}
(0, logging_1.log)('\nPassed all checks.');
}
exports.verifyContract = verifyContract;
function getMetadata(bytecode) {
// The last two bytes should represent the metadata length.
const length = bytecode.length;
const metadataLength = bytecode[length - 2] * 2 ** 8 + bytecode[length - 1];
return bytecode.slice(length - metadataLength);
}