@matterlabs/hardhat-zksync-solc
Version:
Hardhat plugin to compile smart contracts for the ZKsync network
363 lines • 17.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLibraryLink = exports.generateFQN = exports.getLatestEraVersion = exports.getZkVmNormalizedVersion = exports.saveDataToFile = exports.getLatestRelease = exports.download = exports.writeLibrariesToFile = exports.mapMissingLibraryDependencies = exports.findMissingLibraries = exports.generateSolcJSExecutableCode = exports.isVersionForDeprecation = exports.getVersionComponents = exports.pluralize = exports.getZkVmSolcUrl = exports.getZksolcUrl = exports.saltFromUrl = exports.sha1 = exports.isURL = exports.zeroxlify = exports.isBreakableCompilerVersion = exports.updateBreakableCompilerConfig = exports.updateDefaultCompilerConfig = exports.filterSupportedOutputSelections = void 0;
const semver_1 = __importDefault(require("semver"));
const crypto_1 = __importDefault(require("crypto"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const util_1 = __importDefault(require("util"));
const chalk_1 = __importDefault(require("chalk"));
const constants_1 = require("./constants");
const errors_1 = require("./errors");
const config_update_1 = require("./config-update");
const TEMP_FILE_PREFIX = 'tmp-';
function filterSupportedOutputSelections(outputSelection, zkCompilerVersion) {
const filteredOutputSelection = {};
const versionComponents = getVersionComponents(zkCompilerVersion);
let supportedOutputSelections;
switch (true) {
case versionComponents[0] <= 1 && versionComponents[1] <= 3 && versionComponents[2] <= 5:
supportedOutputSelections = constants_1.ZKSOLC_COMPILERS_SELECTOR_MAP['1.3.5'];
break;
default:
supportedOutputSelections = [...constants_1.ZKSOLC_COMPILERS_SELECTOR_MAP['1.3.5'], 'metadata', 'userdoc', 'devdoc'];
break;
}
for (const [file, contractSelection] of Object.entries(outputSelection)) {
filteredOutputSelection[file] = {};
for (const [contract, outputs] of Object.entries(contractSelection)) {
filteredOutputSelection[file][contract] = outputs.filter((output) => supportedOutputSelections.includes(output));
}
}
return filteredOutputSelection;
}
exports.filterSupportedOutputSelections = filterSupportedOutputSelections;
function updateDefaultCompilerConfig(solcConfigData, zksolc) {
const compiler = solcConfigData.compiler;
const settings = compiler.settings || {};
// Override the default solc optimizer settings with zksolc optimizer settings.
compiler.settings = { ...settings, optimizer: { ...zksolc.settings.optimizer } };
zksolc.settings.enableEraVMExtensions = zksolc.settings.enableEraVMExtensions || zksolc.settings.isSystem || false;
zksolc.settings.forceEVMLA = zksolc.settings.forceEVMLA || zksolc.settings.forceEvmla || false;
if (zksolc.settings.isSystem !== undefined) {
console.warn(chalk_1.default.blue(constants_1.COMPILER_ZKSOLC_IS_SYSTEM_USE));
delete zksolc.settings.isSystem;
}
if (zksolc.settings.forceEvmla !== undefined) {
console.warn(chalk_1.default.blue(constants_1.COMPILER_ZKSOLC_FORCE_EVMLA_USE));
delete zksolc.settings.forceEvmla;
}
const [major, minor] = getVersionComponents(compiler.version);
if (major === 0 && minor < 8) {
console.warn(chalk_1.default.blue(constants_1.COMPILER_ZKSOLC_NEED_EVM_CODEGEN));
compiler.settings.forceEVMLA = true;
}
// Remove metadata settings from solidity settings.
delete compiler.settings.metadata;
// Override the solc metadata settings with zksolc metadata settings.
if (zksolc.settings.metadata) {
compiler.settings.metadata = { ...zksolc.settings.metadata };
}
// zkSolc supports only a subset of solc output selections
compiler.settings.outputSelection = filterSupportedOutputSelections(compiler.settings.outputSelection, zksolc.version);
}
exports.updateDefaultCompilerConfig = updateDefaultCompilerConfig;
const solcUpdaters = [
new config_update_1.OverrideCompilerSolcUserConfigUpdater(),
new config_update_1.CompilerSolcUserConfigUpdater(),
];
function updateBreakableCompilerConfig(solcConfigData, zksolc, latestEraVersion, userConfigCompilers) {
const compiler = solcConfigData.compiler;
if (isBreakableCompilerVersion(zksolc.version)) {
compiler.settings.detectMissingLibraries = false;
compiler.settings.forceEVMLA = zksolc.settings.forceEVMLA;
compiler.settings.enableEraVMExtensions = zksolc.settings.enableEraVMExtensions;
compiler.settings.codegen = zksolc.settings.codegen;
compiler.settings.LLVMOptions = zksolc.settings.LLVMOptions;
}
solcUpdaters
.find((updater) => updater.suituble(userConfigCompilers, solcConfigData.file))
?.update(compiler, latestEraVersion, zksolc, userConfigCompilers, solcConfigData.file);
if (zksolc.version !== 'latest' &&
compiler.eraVersion &&
semver_1.default.lt(zksolc.version, constants_1.ZKSOLC_COMPILER_VERSION_MIN_VERSION_WITH_ZKVM_COMPILER)) {
console.warn(chalk_1.default.blue(constants_1.COMPILER_ZKSOLC_VERSION_WITH_ZKVM_SOLC_WARN));
compiler.eraVersion = undefined;
}
}
exports.updateBreakableCompilerConfig = updateBreakableCompilerConfig;
function isBreakableCompilerVersion(zksolcVersion) {
return zksolcVersion === 'latest' || semver_1.default.gte(zksolcVersion, constants_1.ZKSOLC_COMPILER_MIN_VERSION_BREAKABLE_CHANGE);
}
exports.isBreakableCompilerVersion = isBreakableCompilerVersion;
function zeroxlify(hex) {
hex = hex.toLowerCase();
return hex.slice(0, 2) === '0x' ? hex : `0x${hex}`;
}
exports.zeroxlify = zeroxlify;
function isURL(url) {
try {
const locator = new URL(url);
return locator.protocol === 'http:' || locator.protocol === 'https:';
}
catch (e) {
return false;
}
}
exports.isURL = isURL;
function sha1(str) {
return crypto_1.default.createHash('sha1').update(str).digest('hex');
}
exports.sha1 = sha1;
function saltFromUrl(url) {
return sha1(url);
}
exports.saltFromUrl = saltFromUrl;
function getZksolcUrl(repo, version) {
// @ts-ignore
const platform = { darwin: 'macosx', linux: 'linux', win32: 'windows' }[process.platform];
const toolchain = semver_1.default.lt(version, constants_1.COMPILER_MIN_LINUX_VERSION_WITH_GNU_TOOLCHAIN)
? // @ts-ignore
{ linux: '-musl', win32: '-gnu', darwin: '' }[process.platform]
: // @ts-ignore
{ linux: '-gnu', win32: '-gnu', darwin: '' }[process.platform];
const arch = process.arch === 'x64' ? 'amd64' : process.arch;
const ext = process.platform === 'win32' ? '.exe' : '';
return `${repo}/releases/download/${version}/zksolc-${platform}-${arch}${toolchain}-v${version}${ext}`;
}
exports.getZksolcUrl = getZksolcUrl;
function getZkVmSolcUrl(repo, version) {
// @ts-ignore
const platform = { darwin: 'macosx', linux: 'linux', win32: 'windows' }[process.platform];
// @ts-ignore
const arch = process.arch === 'x64' ? 'amd64' : process.arch;
const ext = process.platform === 'win32' ? '.exe' : '';
return `${repo}/releases/download/${version}/solc-${platform}-${arch}-${version}${ext}`;
}
exports.getZkVmSolcUrl = getZkVmSolcUrl;
function pluralize(n, singular, plural) {
if (n === 1) {
return singular;
}
if (plural !== undefined) {
return plural;
}
return `${singular}s`;
}
exports.pluralize = pluralize;
function getVersionComponents(version) {
const versionComponents = version.split('.');
return [parseInt(versionComponents[0], 10), parseInt(versionComponents[1], 10), parseInt(versionComponents[2], 10)];
}
exports.getVersionComponents = getVersionComponents;
function isVersionForDeprecation(version) {
return semver_1.default.lt(version, '1.4.0');
}
exports.isVersionForDeprecation = isVersionForDeprecation;
// Generate SolcJS executable code
function generateSolcJSExecutableCode(solcJsPath, workingDir) {
return constants_1.SOLCJS_EXECUTABLE_CODE.replace(/SOLCJS_PATH/g, solcJsPath).replace(/WORKING_DIR/g, workingDir);
}
exports.generateSolcJSExecutableCode = generateSolcJSExecutableCode;
// Find all the libraries that are missing from the contracts
function findMissingLibraries(zkSolcOutput) {
const missingLibraries = new Set();
for (const filePath in zkSolcOutput.contracts) {
if (!filePath)
continue;
for (const contractName in zkSolcOutput.contracts[filePath]) {
if (!contractName)
continue;
const contract = zkSolcOutput.contracts[filePath][contractName];
if (contract.missingLibraries && contract.missingLibraries.length > 0) {
contract.missingLibraries.forEach((library) => {
missingLibraries.add(library);
});
}
}
}
return missingLibraries;
}
exports.findMissingLibraries = findMissingLibraries;
function mapMissingLibraryDependencies(zkSolcOutput, missingLibraries) {
const dependencyMap = new Array();
missingLibraries.forEach((library) => {
const [libFilePath, libContractName] = library.split(':');
if (zkSolcOutput.contracts[libFilePath] && zkSolcOutput.contracts[libFilePath][libContractName]) {
const contract = zkSolcOutput.contracts[libFilePath][libContractName];
if (contract.missingLibraries) {
dependencyMap.push({
contractName: libContractName,
contractPath: libFilePath,
missingLibraries: contract.missingLibraries,
});
}
}
});
return dependencyMap;
}
exports.mapMissingLibraryDependencies = mapMissingLibraryDependencies;
// Get or create the libraries file. If the file doesn't exist, create it with an empty array
const getOrCreateLibraries = async (filePath) => {
// Ensure the file exists
if (!(await fs_extra_1.default.pathExists(filePath))) {
await fs_extra_1.default.outputFile(filePath, '[]'); // Initialize with an empty array
}
// Return the file's content
return await fs_extra_1.default.readJSON(filePath);
};
// Write missing libraries to file and lock the file while writing
const writeLibrariesToFile = async (filePath, libraries) => {
try {
let existingLibraries = await getOrCreateLibraries(filePath); // Ensure that the file exists
await proper_lockfile_1.default.lock(filePath, { retries: { retries: 10, maxTimeout: 1000 } });
existingLibraries = await getOrCreateLibraries(filePath); // Read again after locking
const combinedLibraries = [...existingLibraries, ...libraries];
fs_extra_1.default.outputFileSync(filePath, JSON.stringify(combinedLibraries, null, 4));
}
catch (e) {
throw new errors_1.ZkSyncSolcPluginError(`Failed to write missing libraries file: ${e}`);
}
finally {
await proper_lockfile_1.default.unlock(filePath);
}
};
exports.writeLibrariesToFile = writeLibrariesToFile;
function resolveTempFileName(filePath) {
const { dir, ext, name } = path_1.default.parse(filePath);
return path_1.default.format({
dir,
ext,
name: `${TEMP_FILE_PREFIX}${name}`,
});
}
async function download(url, filePath, userAgent, timeoutMillis = 10000, extraHeaders = {}) {
const { pipeline } = await Promise.resolve().then(() => __importStar(require('stream')));
const { getGlobalDispatcher, request } = await Promise.resolve().then(() => __importStar(require('undici')));
const streamPipeline = util_1.default.promisify(pipeline);
const dispatcher = getGlobalDispatcher();
// Fetch the url
const response = await request(url, {
dispatcher,
headersTimeout: timeoutMillis,
maxRedirections: 10,
method: 'GET',
headers: {
...extraHeaders,
'User-Agent': `${userAgent}`,
},
});
if (response.statusCode >= 200 && response.statusCode <= 299) {
const tmpFilePath = resolveTempFileName(filePath);
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
await streamPipeline(response.body, fs_1.default.createWriteStream(tmpFilePath));
return fs_extra_1.default.move(tmpFilePath, filePath, { overwrite: true });
}
// undici's response bodies must always be consumed to prevent leaks
const text = await response.body.text();
// eslint-disable-next-line
throw new Error(`Failed to download ${url} - ${response.statusCode} received. ${text}`);
}
exports.download = download;
async function getLatestRelease(owner, repo, userAgent, defaultValue, tagPrefix = 'v', timeout = constants_1.DEFAULT_TIMEOUT_MILISECONDS) {
const url = `https://github.com/${owner}/${repo}/releases/latest`;
const redirectUrlPattern = `https://github.com/${owner}/${repo}/releases/tag/${tagPrefix}`;
const { request } = await Promise.resolve().then(() => __importStar(require('undici')));
try {
const response = await request(url, {
headersTimeout: timeout,
maxRedirections: 0,
method: 'GET',
headers: {
'User-Agent': `${userAgent}`,
},
});
// Check if the response is a redirect
if (response.statusCode >= 300 && response.statusCode < 400) {
// Get the URL from the 'location' header
if (response.headers.location && typeof response.headers.location === 'string') {
// Check if the redirect URL matches the expected pattern
if (response.headers.location.startsWith(redirectUrlPattern)) {
// Extract the tag from the redirect URL
return response.headers.location.substring(redirectUrlPattern.length);
}
throw new errors_1.ZkSyncSolcPluginError(`Unexpected redirect URL: ${response.headers.location} for URL: ${url}`);
}
else {
// Throw an error if the 'location' header is missing in a redirect response
throw new errors_1.ZkSyncSolcPluginError(`Redirect location not found for URL: ${url}`);
}
}
else {
// Throw an error for non-redirect responses
throw new errors_1.ZkSyncSolcPluginError(`Unexpected response status: ${response.statusCode} for URL: ${url}`);
}
}
catch {
return defaultValue;
}
}
exports.getLatestRelease = getLatestRelease;
async function saveDataToFile(data, targetPath) {
await fs_extra_1.default.ensureDir(path_1.default.dirname(targetPath));
await fs_extra_1.default.writeJSON(targetPath, data, { spaces: 2 });
}
exports.saveDataToFile = saveDataToFile;
function getZkVmNormalizedVersion(solcVersion, zkVmSolcVersion) {
return `zkVM-${solcVersion}-${zkVmSolcVersion}`;
}
exports.getZkVmNormalizedVersion = getZkVmNormalizedVersion;
async function getLatestEraVersion() {
return (await getLatestRelease(constants_1.ZKSOLC_BIN_OWNER, constants_1.ZKVM_SOLC_BIN_REPOSITORY_NAME, constants_1.USER_AGENT, constants_1.fallbackLatestEraCompilerVersion, '')).split('-')[1];
}
exports.getLatestEraVersion = getLatestEraVersion;
function generateFQN(sourceName, contractName) {
return `${sourceName}:${contractName}`;
}
exports.generateFQN = generateFQN;
async function getLibraryLink(hre, libraries, contractZbinPath) {
if (libraries === undefined || Object.keys(libraries).length === 0) {
return {
contractZbinPath,
};
}
const populatedLibraries = {};
await Promise.all(Object.entries(libraries).map(async (libraryInfo) => {
const artifact = await hre.artifacts.readArtifact(libraryInfo[0]);
populatedLibraries[generateFQN(artifact.sourceName, artifact.contractName)] = libraryInfo[1];
}));
return {
contractZbinPath,
libraries: populatedLibraries,
};
}
exports.getLibraryLink = getLibraryLink;
//# sourceMappingURL=utils.js.map