@matterlabs/hardhat-zksync-solc
Version:
Hardhat plugin to compile smart contracts for the ZKsync network
601 lines (519 loc) • 21.2 kB
text/typescript
import {
TASK_COMPILE_SOLIDITY_RUN_SOLC,
TASK_COMPILE_SOLIDITY_RUN_SOLCJS,
TASK_COMPILE_SOLIDITY_GET_ARTIFACT_FROM_COMPILATION_OUTPUT,
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD,
TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOBS,
TASK_COMPILE_SOLIDITY_LOG_COMPILATION_RESULT,
TASK_COMPILE_SOLIDITY_LOG_DOWNLOAD_COMPILER_START,
TASK_COMPILE_SOLIDITY_LOG_RUN_COMPILER_START,
TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES,
TASK_COMPILE_REMOVE_OBSOLETE_ARTIFACTS,
TASK_COMPILE_SOLIDITY_COMPILE_SOLC,
TASK_COMPILE_SOLIDITY_LOG_RUN_COMPILER_END,
TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS,
TASK_COMPILE,
TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT,
} from 'hardhat/builtin-tasks/task-names';
import { extendEnvironment, extendConfig, subtask, task, types } from 'hardhat/internal/core/config/config-env';
import { getCompilersDir } from 'hardhat/internal/util/global-dir';
import './type-extensions';
import { Artifacts, getArtifactFromContractOutput } from 'hardhat/internal/artifacts';
import { Mutex } from 'hardhat/internal/vendor/await-semaphore';
import fs from 'fs';
import chalk from 'chalk';
import semver from 'semver';
import {
ArtifactsEmittedPerFile,
CompilationJob,
CompilerInput,
CompilerOutput,
HardhatRuntimeEnvironment,
RunSuperFunction,
SolcBuild,
TaskArguments,
} from 'hardhat/types';
import debug from 'debug';
import { compile } from './compile';
import {
zeroxlify,
getZksolcUrl,
pluralize,
saltFromUrl,
generateSolcJSExecutableCode,
updateDefaultCompilerConfig,
getZkVmNormalizedVersion,
updateBreakableCompilerConfig,
} from './utils';
import {
defaultZkSolcConfig,
ZKSOLC_BIN_REPOSITORY,
ZK_ARTIFACT_FORMAT_VERSION,
COMPILING_INFO_MESSAGE,
MISSING_LIBRARIES_NOTICE,
COMPILE_AND_DEPLOY_LIBRARIES_INSTRUCTIONS,
MISSING_LIBRARY_LINK,
COMPILING_INFO_MESSAGE_ZKVM_SOLC,
ZKSOLC_COMPILER_PATH_VERSION,
TASK_UPDATE_SOLIDITY_COMPILERS,
TASK_DOWNLOAD_ZKSOLC,
TASK_COMPILE_LINK,
} from './constants';
import { ZksolcCompilerDownloader } from './compile/downloader';
import { ZkVmSolcCompilerDownloader } from './compile/zkvm-solc-downloader';
import {
SolcMultiUserConfigExtractor,
SolcSoloUserConfigExtractor,
SolcStringUserConfigExtractor,
SolcUserConfigExtractor,
} from './config-extractor';
import { FactoryDeps, ZkSyncCompilerInput } from './types';
import { compileLink } from './plugin';
import '@matterlabs/hardhat-zksync-telemetry';
const logDebug = debug('hardhat:core:tasks:compile');
const extractors: SolcUserConfigExtractor[] = [
new SolcStringUserConfigExtractor(),
new SolcSoloUserConfigExtractor(),
new SolcMultiUserConfigExtractor(),
];
const zkVmSolcCompilerDownloaderMutex = new Mutex();
const zkSolcCompilerDownloaderMutex = new Mutex();
extendConfig((config, userConfig) => {
if (userConfig.zksolc?.settings?.compilerPath) {
defaultZkSolcConfig.version = ZKSOLC_COMPILER_PATH_VERSION;
}
config.zksolc = { ...defaultZkSolcConfig, ...userConfig?.zksolc };
config.zksolc.settings = { ...defaultZkSolcConfig.settings, ...userConfig?.zksolc?.settings };
config.zksolc.settings.optimizer = {
...defaultZkSolcConfig.settings.optimizer,
...userConfig?.zksolc?.settings?.optimizer,
};
config.zksolc.settings.libraries = {
...defaultZkSolcConfig.settings.libraries,
...userConfig?.zksolc?.settings?.libraries,
};
});
extendEnvironment((hre) => {
if (hre.network.config.zksync) {
hre.network.zksync = hre.network.config.zksync;
let artifactsPath = hre.config.paths.artifacts;
if (!artifactsPath.endsWith('-zk')) {
artifactsPath = `${artifactsPath}-zk`;
}
let cachePath = hre.config.paths.cache;
if (!cachePath.endsWith('-zk')) {
cachePath = `${cachePath}-zk`;
}
// Forcibly update the artifacts object.
hre.config.paths.artifacts = artifactsPath;
hre.config.paths.cache = cachePath;
(hre as any).artifacts = new Artifacts(artifactsPath);
hre.config.solidity.compilers.forEach(async (compiler) =>
updateDefaultCompilerConfig({ compiler }, hre.config.zksolc),
);
for (const [file, compiler] of Object.entries(hre.config.solidity.overrides)) {
updateDefaultCompilerConfig({ compiler, file }, hre.config.zksolc);
}
}
});
task(TASK_COMPILE).setAction(
async (compilationArgs: any, hre: HardhatRuntimeEnvironment, runSuper: RunSuperFunction<TaskArguments>) => {
if (hre.network.zksync) {
await hre.run(TASK_DOWNLOAD_ZKSOLC);
await hre.run(TASK_UPDATE_SOLIDITY_COMPILERS);
}
await runSuper(compilationArgs);
},
);
subtask(TASK_COMPILE_LINK)
.addParam('sourceName', 'Source name of the artifact')
.addParam('contractName', 'Contract name of the artifact')
.addOptionalParam('libraries', undefined, undefined, types.any)
.addFlag('withoutError', undefined)
.setAction(compileLink);
subtask(TASK_DOWNLOAD_ZKSOLC, async (_args: any, hre: HardhatRuntimeEnvironment) => {
if (!hre.network.zksync) {
return;
}
const compilersCache = await getCompilersDir();
await zkSolcCompilerDownloaderMutex.use(async () => {
const zksolcDownloader = await ZksolcCompilerDownloader.getDownloaderWithVersionValidated(
hre.config.zksolc.version,
hre.config.zksolc.settings.compilerPath ?? '',
compilersCache,
);
const isZksolcDownloaded = await zksolcDownloader.isCompilerDownloaded();
if (!isZksolcDownloaded) {
await zksolcDownloader.downloadCompiler();
}
hre.config.zksolc.settings.compilerPath = zksolcDownloader.getCompilerPath();
hre.config.zksolc.version = zksolcDownloader.getVersion();
});
});
subtask(TASK_UPDATE_SOLIDITY_COMPILERS, async (_args: any, hre: HardhatRuntimeEnvironment) => {
if (!hre.network.zksync) {
return;
}
const userSolidityConfig = hre.userConfig.solidity;
const extractedConfigs = extractors
.find((extractor) => extractor.suitable(userSolidityConfig))
?.extract(userSolidityConfig);
hre.config.solidity.compilers.forEach(async (compiler) =>
updateBreakableCompilerConfig(
{ compiler },
hre.config.zksolc,
semver.gte(hre.config.zksolc.version, '1.5.13') ? '1.0.2' : '1.0.1',
extractedConfigs?.compilers ?? [],
),
);
for (const [file, compiler] of Object.entries(hre.config.solidity.overrides)) {
updateBreakableCompilerConfig(
{ compiler, file },
hre.config.zksolc,
semver.gte(hre.config.zksolc.version, '1.5.13') ? '1.0.2' : '1.0.1',
extractedConfigs?.overides ?? new Map(),
);
}
});
subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, async (args: { sourcePaths: string[] }, hre, runSuper) => {
if (hre.network.zksync !== true) {
return await runSuper(args);
}
if (hre.config.zksolc.settings.forceContractsToCompile) {
return hre.config.zksolc.settings.forceContractsToCompile;
}
const contractsToCompile: string[] | undefined = hre.config.zksolc.settings.contractsToCompile;
if (!contractsToCompile || contractsToCompile.length === 0) {
return await runSuper(args);
}
const sourceNames: string[] = await runSuper(args);
return sourceNames.filter((sourceName) =>
contractsToCompile.some((contractToCompile) => sourceName.includes(contractToCompile)),
);
});
// This override is needed to invalidate cache when zksolc config is changed.
subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOBS, async (args, hre, runSuper) => {
const { jobs, errors } = await runSuper(args);
if (hre.network.zksync !== true || hre.config.zksolc.compilerSource !== 'binary') {
return { jobs, errors };
}
jobs.forEach((job: any) => {
job.solidityConfig.zksolc = hre.config.zksolc;
job.solidityConfig.zksolc.settings.compilerPath = hre.config.zksolc.settings.compilerPath;
if (hre.config.zksolc.settings.libraries) {
job.solidityConfig.settings.libraries = hre.config.zksolc.settings.libraries;
}
});
return { jobs, errors };
});
subtask(
TASK_COMPILE_SOLIDITY_GET_ARTIFACT_FROM_COMPILATION_OUTPUT,
async (
{
sourceName,
contractName,
contractOutput,
}: {
sourceName: string;
contractName: string;
contractOutput: any;
},
hre,
): Promise<any> => {
if (hre.network.zksync !== true) {
return getArtifactFromContractOutput(sourceName, contractName, contractOutput);
}
let bytecode: string =
contractOutput.evm?.bytecode?.object || contractOutput.evm?.deployedBytecode?.object || '';
bytecode = zeroxlify(bytecode);
const factoryDeps: FactoryDeps = {};
const entries: Array<[string, string]> = Object.entries(contractOutput.factoryDependencies || {});
for (const [hash, dependency] of entries) {
factoryDeps[zeroxlify(hash)] = dependency;
}
return {
_format: ZK_ARTIFACT_FORMAT_VERSION,
contractName,
sourceName,
abi: contractOutput.abi,
// technically, zkEVM has no difference between bytecode & deployedBytecode,
// but both fields are included just in case
bytecode,
deployedBytecode: bytecode,
// zksolc does not support unlinked objects,
// all external libraries are either linked during compilation or inlined
linkReferences: {},
deployedLinkReferences: {},
// ZKsync-specific field
factoryDeps,
};
},
);
subtask(TASK_COMPILE_SOLIDITY_RUN_SOLC, async (args: { input: any; solcPath: string }, hre, runSuper) => {
if (hre.network.zksync !== true) {
return await runSuper(args);
}
return await compile(hre.config.zksolc, args.input, args.solcPath);
});
subtask(TASK_COMPILE_SOLIDITY_RUN_SOLCJS, async (args: { input: any; solcJsPath: string }, hre, runSuper) => {
if (hre.network.zksync !== true) {
return await runSuper(args);
}
const solcPath = `${args.solcJsPath}.executable`;
if (!fs.existsSync(solcPath)) {
const solcJsExecutableCode = generateSolcJSExecutableCode(args.solcJsPath, process.cwd());
fs.writeFileSync(solcPath, Buffer.from(solcJsExecutableCode), { encoding: 'utf-8', flag: 'w' });
fs.chmodSync(solcPath, '755');
}
return await compile(hre.config.zksolc, args.input, solcPath);
});
/*
* This task is overriden to:
* - use valid zkvm solc version if that is needed and return valid SolcBuild object
*/
subtask(
TASK_COMPILE_SOLIDITY_COMPILE_SOLC,
async (
args: {
input: CompilerInput;
quiet: boolean;
solcVersion: string;
compilationJob: CompilationJob;
compilationJobs: CompilationJob[];
compilationJobIndex: number;
},
hre,
runSuper,
): Promise<{ output: CompilerOutput; solcBuild: SolcBuild }> => {
if (hre.network.zksync !== true) {
return await runSuper(args);
}
const solcBuild: SolcBuild = await hre.run(TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD, {
quiet: args.quiet,
solcVersion: args.solcVersion,
compilationJob: args.compilationJob,
});
await hre.run(TASK_COMPILE_SOLIDITY_LOG_RUN_COMPILER_START, {
compilationJob: args.compilationJob,
compilationJobs: args.compilationJobs,
compilationJobIndex: args.compilationJobIndex,
quiet: args.quiet,
});
let output;
if (solcBuild.isSolcJs) {
output = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLCJS, {
input: args.input,
solcJsPath: solcBuild.compilerPath,
});
} else {
output = await hre.run(TASK_COMPILE_SOLIDITY_RUN_SOLC, {
input: args.input,
solcPath: solcBuild.compilerPath,
});
}
await hre.run(TASK_COMPILE_SOLIDITY_LOG_RUN_COMPILER_END, {
compilationJob: args.compilationJob,
compilationJobs: args.compilationJobs,
compilationJobIndex: args.compilationJobIndex,
output,
quiet: args.quiet,
});
return { output, solcBuild };
},
);
// This task is overriden to:
// - prevent unnecessary solc downloads when using docker
// - download zksolc binary if needed
// - validate zksolc binary
subtask(
TASK_COMPILE_SOLIDITY_GET_SOLC_BUILD,
async (args: { solcVersion: string; compilationJob: CompilationJob }, hre, runSuper) => {
if (hre.network.zksync !== true) {
return await runSuper(args);
}
if (hre.config.zksolc.compilerSource === 'docker') {
// Versions are wrong here when using docker, because there is no
// way to know them beforehand except to run the docker image, which
// adds 5-10 seconds to startup time. We cannot read them from artifacts,
// since that would make cache invalid every time, if the version is
// different from the one in the docker image.
//
// If you wish to know the actual versions from build-info files,
// please look at `output.version`, `output.long_version`
// and `output.zk_version` in the generated JSON.
return {
compilerPath: '',
isSolcJs: false,
version: args.solcVersion,
longVersion: '',
};
}
const compiler = args.compilationJob?.getSolcConfig();
if (compiler && compiler.eraVersion) {
const compilersCache = await getCompilersDir();
let compilerPath: string = '';
let version: string = '';
let normalizedVersion: string = '';
await zkVmSolcCompilerDownloaderMutex.use(async () => {
const zkVmSolcCompilerDownloader = await ZkVmSolcCompilerDownloader.getDownloaderWithVersionValidated(
compiler.eraVersion!,
compiler.version,
compilersCache,
);
const isZksolcDownloaded = await zkVmSolcCompilerDownloader.isCompilerDownloaded();
if (!isZksolcDownloaded) {
await zkVmSolcCompilerDownloader.downloadCompiler();
}
compilerPath = zkVmSolcCompilerDownloader.getCompilerPath();
version = zkVmSolcCompilerDownloader.getVersion();
normalizedVersion = getZkVmNormalizedVersion(
zkVmSolcCompilerDownloader.getSolcVersion(),
zkVmSolcCompilerDownloader.getZkVmSolcVersion(),
);
});
console.info(chalk.yellow(COMPILING_INFO_MESSAGE_ZKVM_SOLC(hre.config.zksolc.version, version)));
return {
compilerPath,
isSolcJs: false,
version,
longVersion: normalizedVersion,
};
} else {
const solcBuild = await runSuper(args);
console.info(chalk.yellow(COMPILING_INFO_MESSAGE(hre.config.zksolc.version, args.solcVersion)));
return solcBuild;
}
},
);
subtask(
TASK_COMPILE_SOLIDITY_LOG_COMPILATION_RESULT,
async ({ compilationJobs }: { compilationJobs: CompilationJob[] }, hre, _runSuper) => {
if (hre.config.zksolc.settings.areLibrariesMissing) {
console.info(chalk.yellow(MISSING_LIBRARIES_NOTICE));
console.info(chalk.red(COMPILE_AND_DEPLOY_LIBRARIES_INSTRUCTIONS));
console.info(chalk.yellow(MISSING_LIBRARY_LINK));
} else {
let count = 0;
for (const job of compilationJobs) {
count += job.getResolvedFiles().filter((file) => job.emitsArtifacts(file)).length;
}
if (count > 0) {
console.info(chalk.green(`Successfully compiled ${count} Solidity ${pluralize(count, 'file')}`));
}
}
},
);
subtask(TASK_COMPILE_SOLIDITY_LOG_DOWNLOAD_COMPILER_START).setAction(
async ({
isCompilerDownloaded,
solcVersion,
}: {
isCompilerDownloaded: boolean;
quiet: boolean;
solcVersion: string;
}) => {
if (isCompilerDownloaded) {
return;
}
console.info(chalk.yellow(`Downloading solc ${solcVersion}`));
},
);
subtask(TASK_COMPILE_SOLIDITY_LOG_RUN_COMPILER_START).setAction(
async ({
compilationJob,
}: {
compilationJob: CompilationJob;
compilationJobs: CompilationJob[];
compilationJobIndex: number;
}) => {
const count = compilationJob.getResolvedFiles().length;
if (count > 0) {
console.info(chalk.yellow(`Compiling ${count} Solidity ${pluralize(count, 'file')}`));
}
},
);
subtask(TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS).setAction(
async (
{
compilationJob,
input,
output,
solcBuild,
}: {
compilationJob: CompilationJob;
input: CompilerInput;
output: CompilerOutput;
solcBuild: SolcBuild;
},
{ artifacts, run, network },
runSuper,
): Promise<{
artifactsEmittedPerFile: ArtifactsEmittedPerFile;
}> => {
if (network.zksync !== true) {
return await runSuper({
compilationJob,
input,
output,
solcBuild,
});
}
const version: string = compilationJob.getSolcConfig().eraVersion
? getZkVmNormalizedVersion(
compilationJob.getSolcConfig().version,
compilationJob.getSolcConfig().eraVersion!,
)
: compilationJob.getSolcConfig().version;
const pathToBuildInfo = await artifacts.saveBuildInfo(version, solcBuild.longVersion, input, output);
const artifactsEmittedPerFile: ArtifactsEmittedPerFile = await Promise.all(
compilationJob
.getResolvedFiles()
.filter((f) => compilationJob.emitsArtifacts(f))
.map(async (file) => {
const artifactsEmitted = await Promise.all(
Object.entries(output.contracts?.[file.sourceName] ?? {}).map(
async ([contractName, contractOutput]) => {
logDebug(`Emitting artifact for contract '${contractName}'`);
const artifact = await run(TASK_COMPILE_SOLIDITY_GET_ARTIFACT_FROM_COMPILATION_OUTPUT, {
sourceName: file.sourceName,
contractName,
contractOutput,
});
await artifacts.saveArtifactAndDebugFile(artifact, pathToBuildInfo);
return artifact.contractName;
},
),
);
return {
file,
artifactsEmitted,
};
}),
);
return { artifactsEmittedPerFile };
},
);
subtask(TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT, async (taskArgs, hre, runSuper) => {
const compilerInput: ZkSyncCompilerInput = await runSuper(taskArgs);
if (hre.network.zksync !== true) {
return compilerInput;
}
if (hre.config.zksolc.settings.suppressedErrors && hre.config.zksolc.settings.suppressedErrors.length > 0) {
compilerInput.suppressedErrors = hre.config.zksolc.settings.suppressedErrors;
}
if (hre.config.zksolc.settings.suppressedWarnings && hre.config.zksolc.settings.suppressedWarnings.length > 0) {
compilerInput.suppressedWarnings = hre.config.zksolc.settings.suppressedWarnings;
}
return compilerInput;
});
subtask(TASK_COMPILE_REMOVE_OBSOLETE_ARTIFACTS, async (taskArgs, hre, runSuper) => {
if (hre.network.zksync !== true || !hre.config.zksolc.settings.areLibrariesMissing) {
return await runSuper(taskArgs);
}
// Delete all artifacts and cache files because there are missing libraries and the compilation output is invalid.
const artifactsDir = hre.config.paths.artifacts;
const cacheDir = hre.config.paths.cache;
fs.rmSync(artifactsDir, { recursive: true });
fs.rmSync(cacheDir, { recursive: true });
});
export { getZksolcUrl, ZKSOLC_BIN_REPOSITORY, saltFromUrl };