From 1a8593feea847ce2d26cc2394050da8fed631943 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Fri, 13 Sep 2024 01:17:36 +0200 Subject: [PATCH 1/5] feat: support for deploy time library linking --- .../deploy/001_deploy.ts | 3 +- .../hardhat.config.ts | 2 +- .../src/deployer-extension.ts | 4 +- .../src/deployer-helper.ts | 18 +++++- .../hardhat-zksync-deploy/src/deployer.ts | 4 +- packages/hardhat-zksync-deploy/src/types.ts | 5 ++ packages/hardhat-zksync-solc/package.json | 2 + .../hardhat-zksync-solc/src/compile/binary.ts | 29 ++++++++- .../hardhat-zksync-solc/src/compile/index.ts | 31 ++++++++- packages/hardhat-zksync-solc/src/constants.ts | 3 + packages/hardhat-zksync-solc/src/index.ts | 63 +++++++++++++++++-- packages/hardhat-zksync-solc/src/types.ts | 9 +++ packages/hardhat-zksync-solc/src/utils.ts | 43 ++++++++++++- pnpm-lock.yaml | 6 ++ 14 files changed, 204 insertions(+), 18 deletions(-) diff --git a/examples/noninline-libraries-example/deploy/001_deploy.ts b/examples/noninline-libraries-example/deploy/001_deploy.ts index d343233e7..13c0b97b1 100644 --- a/examples/noninline-libraries-example/deploy/001_deploy.ts +++ b/examples/noninline-libraries-example/deploy/001_deploy.ts @@ -18,7 +18,8 @@ export default async function (hre: HardhatRuntimeEnvironment) { // Deploy this contract. The returned object will be of a `Contract` type, similarly to ones in `ethers`. // `greeting` is an argument for contract constructor. - const greeterContract = await deployer.deploy(artifact, []); + const greeterContract = await deployer.deploy(artifact, [], 'create', { + }); // Show the contract info. const contractAddress = await greeterContract.getAddress(); diff --git a/examples/noninline-libraries-example/hardhat.config.ts b/examples/noninline-libraries-example/hardhat.config.ts index 212dacf92..4e92b844f 100644 --- a/examples/noninline-libraries-example/hardhat.config.ts +++ b/examples/noninline-libraries-example/hardhat.config.ts @@ -14,7 +14,7 @@ const config: HardhatUserConfig = { url: 'http://0.0.0.0:8545', }, zkSyncNetwork: { - url: 'http://0.0.0.0:3050', + url: 'http://0.0.0.0:8011', ethNetwork: 'ethNetwork', zksync: true, }, diff --git a/packages/hardhat-zksync-deploy/src/deployer-extension.ts b/packages/hardhat-zksync-deploy/src/deployer-extension.ts index fd08b078d..0ee27053d 100644 --- a/packages/hardhat-zksync-deploy/src/deployer-extension.ts +++ b/packages/hardhat-zksync-deploy/src/deployer-extension.ts @@ -2,7 +2,7 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import * as zk from 'zksync-ethers'; import * as ethers from 'ethers'; import { AbstractDeployer } from './abstract-deployer'; -import { ZkSyncArtifact } from './types'; +import { ZkSyncArtifact, ZkSyncOverrides } from './types'; import { Providers, createProviders, @@ -30,7 +30,7 @@ export class DeployerExtension implements AbstractDeployer { contractNameOrArtifact: ZkSyncArtifact | string, constructorArguments: any[] = [], deploymentType?: zk.types.DeploymentType, - overrides?: ethers.Overrides, + overrides?: ZkSyncOverrides, additionalFactoryDeps?: ethers.BytesLike[], ): Promise { if (!this.wallet) { diff --git a/packages/hardhat-zksync-deploy/src/deployer-helper.ts b/packages/hardhat-zksync-deploy/src/deployer-helper.ts index 080c740b8..3fc2da9f1 100644 --- a/packages/hardhat-zksync-deploy/src/deployer-helper.ts +++ b/packages/hardhat-zksync-deploy/src/deployer-helper.ts @@ -2,7 +2,7 @@ import { HardhatRuntimeEnvironment, HttpNetworkConfig, Network, NetworksConfig } import { DeploymentType } from 'zksync-ethers/build/types'; import * as zk from 'zksync-ethers'; import * as ethers from 'ethers'; -import { ZkSyncArtifact } from './types'; +import { ZkSyncArtifact, ZkSyncOverrides } from './types'; import { ZkSyncDeployPluginError } from './errors'; import { isHttpNetworkConfig, isValidEthNetworkURL } from './utils'; import { loadCache, saveCache } from './deployment-saver'; @@ -64,9 +64,11 @@ export async function deploy( constructorArguments: any[] = [], zkWallet: zk.Wallet, deploymentType: DeploymentType = 'create', - overrides?: ethers.Overrides, + overrides?: ZkSyncOverrides, additionalFactoryDeps?: ethers.BytesLike[], ): Promise { + await linkLibrariesIfNeeded(hre, contractNameOrArtifact, overrides?.libraries); + const artifact: ZkSyncArtifact = typeof contractNameOrArtifact === 'string' ? await loadArtifact(hre, contractNameOrArtifact) @@ -203,6 +205,18 @@ export async function _extractFactoryDepsRecursive( return factoryDeps; } +export async function linkLibrariesIfNeeded( + hre: HardhatRuntimeEnvironment, + contractNameOrArtifact: ZkSyncArtifact | string, + libraries?: { [libraryName: string]: string }, +) { + const artifact: ZkSyncArtifact = + typeof contractNameOrArtifact === 'string' + ? await loadArtifact(hre, contractNameOrArtifact) + : contractNameOrArtifact; + await hre.run('compile:link', { sourceName: artifact.sourceName, contractName: artifact.contractName, libraries }); +} + export function createProviders( networks: NetworksConfig, network: Network, diff --git a/packages/hardhat-zksync-deploy/src/deployer.ts b/packages/hardhat-zksync-deploy/src/deployer.ts index 62da0d4c6..1a934dc1e 100644 --- a/packages/hardhat-zksync-deploy/src/deployer.ts +++ b/packages/hardhat-zksync-deploy/src/deployer.ts @@ -2,7 +2,7 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import * as zk from 'zksync-ethers'; import * as ethers from 'ethers'; -import { ZkSyncArtifact } from './types'; +import { ZkSyncArtifact, ZkSyncOverrides } from './types'; import { Providers, createProviders, @@ -56,7 +56,7 @@ export class Deployer implements AbstractDeployer { contractNameOrArtifact: ZkSyncArtifact | string, constructorArguments: any[] = [], deploymentType?: zk.types.DeploymentType, - overrides?: ethers.Overrides, + overrides?: ZkSyncOverrides, additionalFactoryDeps?: ethers.BytesLike[], ): Promise { return await deploy( diff --git a/packages/hardhat-zksync-deploy/src/types.ts b/packages/hardhat-zksync-deploy/src/types.ts index 3c375c30b..a5b10ed4c 100644 --- a/packages/hardhat-zksync-deploy/src/types.ts +++ b/packages/hardhat-zksync-deploy/src/types.ts @@ -1,3 +1,4 @@ +import { ethers } from 'ethers'; import { Artifact } from 'hardhat/types'; /** @@ -42,3 +43,7 @@ export interface ContractFullQualifiedName { export interface DeployerAccount { [networkName: string]: number | undefined; } + +export interface ZkSyncOverrides extends ethers.Overrides { + libraries?: { [libraryName: string]: string }; +} diff --git a/packages/hardhat-zksync-solc/package.json b/packages/hardhat-zksync-solc/package.json index 9ba275866..99c1e04cc 100644 --- a/packages/hardhat-zksync-solc/package.json +++ b/packages/hardhat-zksync-solc/package.json @@ -41,6 +41,7 @@ "undici": "^6.18.2", "debug": "^4.3.5", "semver": "^7.6.2", + "lodash": "^4.17.21", "sinon": "^18.0.0", "sinon-chai": "^3.7.0", "proper-lockfile": "^4.1.2" @@ -56,6 +57,7 @@ "@types/sinon-chai": "^3.2.12", "@types/debug": "^4.1.12", "@types/proper-lockfile": "^4.1.4", + "@types/lodash": "^4.14.202", "@typescript-eslint/eslint-plugin": "^7.12.0", "@typescript-eslint/parser": "^7.12.0", "eslint": "^8.56.0", diff --git a/packages/hardhat-zksync-solc/src/compile/binary.ts b/packages/hardhat-zksync-solc/src/compile/binary.ts index faa195afa..309ab8792 100644 --- a/packages/hardhat-zksync-solc/src/compile/binary.ts +++ b/packages/hardhat-zksync-solc/src/compile/binary.ts @@ -1,5 +1,5 @@ import { exec } from 'child_process'; -import { ZkSolcConfig } from '../types'; +import { LinkLibraries, ZkSolcConfig } from '../types'; import { isBreakableCompilerVersion } from '../utils'; export async function compileWithBinary( @@ -45,3 +45,30 @@ export async function compileWithBinary( return JSON.parse(output); } + +export async function linkWithBinary(config: ZkSolcConfig, linkLibraries: LinkLibraries): Promise { + const { compilerPath } = config.settings; + + let processCommand = `${compilerPath} --link ${linkLibraries.contractZbinPath}`; + if (linkLibraries.libraries) { + processCommand += ` --libraries ${Object.entries(linkLibraries.libraries) + .map((lib) => `${lib[0]}=${lib[1]}`) + .join(',')}`; + } + const output: string = await new Promise((resolve, reject) => { + const process = exec( + processCommand, + { + maxBuffer: 1024 * 1024 * 500, + }, + (err, stdout, _stderr) => { + if (err !== null) { + return reject(err); + } + resolve(stdout); + }, + ); + process.stdin!.end(); + }); + return JSON.parse(output); +} diff --git a/packages/hardhat-zksync-solc/src/compile/index.ts b/packages/hardhat-zksync-solc/src/compile/index.ts index fa8562e47..9741e724e 100644 --- a/packages/hardhat-zksync-solc/src/compile/index.ts +++ b/packages/hardhat-zksync-solc/src/compile/index.ts @@ -1,7 +1,7 @@ import { HardhatDocker, Image } from '@nomiclabs/hardhat-docker'; import semver from 'semver'; import { CompilerInput } from 'hardhat/types'; -import { ZkSolcConfig } from '../types'; +import { LinkLibraries, ZkSolcConfig } from '../types'; import { ZkSyncSolcPluginError } from '../errors'; import { findMissingLibraries, mapMissingLibraryDependencies, writeLibrariesToFile } from '../utils'; import { @@ -16,7 +16,7 @@ import { compileWithDocker, getSolcVersion, } from './docker'; -import { compileWithBinary } from './binary'; +import { compileWithBinary, linkWithBinary } from './binary'; export async function compile(zksolcConfig: ZkSolcConfig, input: CompilerInput, solcPath?: string) { let compiler: ICompiler; @@ -42,8 +42,21 @@ export async function compile(zksolcConfig: ZkSolcConfig, input: CompilerInput, return await compiler.compile(input, zksolcConfig); } +export async function link(zksolcConfig: ZkSolcConfig, linkLibraries: LinkLibraries) { + let compiler: ICompiler; + + if (zksolcConfig.compilerSource === 'binary') { + compiler = new BinaryCompiler(''); + } else { + throw new ZkSyncSolcPluginError(`Incorrect compiler source: ${zksolcConfig.compilerSource}`); + } + + return await compiler.link(zksolcConfig, linkLibraries); +} + export interface ICompiler { compile(input: CompilerInput, config: ZkSolcConfig): Promise; + link(config: ZkSolcConfig, linkLibraries: LinkLibraries): Promise; } export class BinaryCompiler implements ICompiler { @@ -51,7 +64,10 @@ export class BinaryCompiler implements ICompiler { public async compile(input: CompilerInput, config: ZkSolcConfig) { // Check for missing libraries - if (semver.gte(config.version, DETECT_MISSING_LIBRARY_MODE_COMPILER_VERSION)) { + if ( + semver.gte(config.version, DETECT_MISSING_LIBRARY_MODE_COMPILER_VERSION) && + semver.lt(config.version, '1.6.0') + ) { const zkSolcOutput = await compileWithBinary(input, config, this.solcPath, true); const missingLibraries = findMissingLibraries(zkSolcOutput); @@ -69,9 +85,14 @@ export class BinaryCompiler implements ICompiler { return zkSolcOutput; } } + config.settings.areLibrariesMissing = false; return await compileWithBinary(input, config, this.solcPath); } + + public async link(config: ZkSolcConfig, linkLibraries: LinkLibraries) { + return await linkWithBinary(config, linkLibraries); + } } export class DockerCompiler implements ICompiler { @@ -101,4 +122,8 @@ export class DockerCompiler implements ICompiler { const version = longVersion.split('+')[0]; return { version, longVersion }; } + + public async link(_: ZkSolcConfig, __: LinkLibraries) { + throw new ZkSyncSolcPluginError('Linking is not supported with docker compiler'); + } } diff --git a/packages/hardhat-zksync-solc/src/constants.ts b/packages/hardhat-zksync-solc/src/constants.ts index f8cc69867..223e8fa29 100644 --- a/packages/hardhat-zksync-solc/src/constants.ts +++ b/packages/hardhat-zksync-solc/src/constants.ts @@ -14,6 +14,9 @@ export const TASK_DOWNLOAD_ZKSOLC = 'compile:zksolc:download'; export const ZKSOLC_COMPILER_PATH_VERSION = 'local_or_remote'; +export const TASK_COMPILE_LINK: string = 'compile:link'; +export const ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING = '1.6.0'; + export const defaultZkSolcConfig: ZkSolcConfig = { version: 'latest', compilerSource: 'binary', diff --git a/packages/hardhat-zksync-solc/src/index.ts b/packages/hardhat-zksync-solc/src/index.ts index e748956e5..ca0daf1e6 100644 --- a/packages/hardhat-zksync-solc/src/index.ts +++ b/packages/hardhat-zksync-solc/src/index.ts @@ -14,7 +14,7 @@ import { TASK_COMPILE_SOLIDITY_EMIT_ARTIFACTS, TASK_COMPILE, } from 'hardhat/builtin-tasks/task-names'; -import { extendEnvironment, extendConfig, subtask, task } from 'hardhat/internal/core/config/config-env'; +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'; @@ -32,7 +32,10 @@ import { TaskArguments, } from 'hardhat/types'; import debug from 'debug'; -import { compile } from './compile'; +import semver from 'semver'; +import lodash from 'lodash'; +import path from 'path'; +import { compile, link } from './compile'; import { zeroxlify, getZksolcUrl, @@ -43,6 +46,9 @@ import { getZkVmNormalizedVersion, updateBreakableCompilerConfig, getLatestEraVersion, + getLibraryLink, + generateFQN, + replaceArtifactBytecodeAndSave, } from './utils'; import { defaultZkSolcConfig, @@ -56,6 +62,8 @@ import { ZKSOLC_COMPILER_PATH_VERSION, TASK_UPDATE_SOLIDITY_COMPILERS, TASK_DOWNLOAD_ZKSOLC, + TASK_COMPILE_LINK, + ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING, } from './constants'; import { ZksolcCompilerDownloader } from './compile/downloader'; import { ZkVmSolcCompilerDownloader } from './compile/zkvm-solc-downloader'; @@ -66,6 +74,7 @@ import { SolcUserConfigExtractor, } from './config-extractor'; import { FactoryDeps } from './types'; +import { ZkSyncSolcPluginError } from './errors'; const logDebug = debug('hardhat:core:tasks:compile'); @@ -132,6 +141,50 @@ task(TASK_COMPILE).setAction( }, ); +subtask(TASK_COMPILE_LINK) + .addParam('sourceName', 'Source name') + .addParam('contractName', 'ContractName') + .addOptionalParam('libraries', undefined, undefined, types.any) + .setAction(async ({ sourceName, contractName, libraries }, hre: HardhatRuntimeEnvironment) => { + if (!hre.network.zksync) { + throw new ZkSyncSolcPluginError('This task is only available for zkSync network'); + } + + await hre.run(TASK_DOWNLOAD_ZKSOLC); + await hre.run(TASK_UPDATE_SOLIDITY_COMPILERS); + + if (semver.lt(hre.config.zksolc.version, ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING)) { + throw new ZkSyncSolcPluginError( + `Linking libraries is only supported for zksolc compiler version ${ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING} or higher`, + ); + } + const contractFQN = generateFQN(sourceName, contractName); + const contractFilePath = path.join(hre.config.paths.artifacts, sourceName, `${contractName}.zbin`); + const artifact = await hre.artifacts.readArtifact(contractFQN); + + fs.writeFileSync(contractFilePath, artifact.bytecode); + + const output = await link(hre.config.zksolc, await getLibraryLink(hre, libraries, contractFilePath)); + + if (!lodash.isEmpty(output.unlinked)) { + throw new ZkSyncSolcPluginError( + `Libraries for contract ${contractFQN} are not linked: ${Object.values(output.unlinked[contractFQN]) + .map((lib) => `${lib}`) + .join(', ')}`, + ); + } + + if (!lodash.isEmpty(output.ignored)) { + console.warn( + `Linking of libraries for contract ${contractFQN} are ignored, delete artifacts and cache folders and try again with new library addresses`, + ); + } + + await replaceArtifactBytecodeAndSave(hre, artifact, contractFilePath); + + return output; + }); + subtask(TASK_DOWNLOAD_ZKSOLC, async (_args: any, hre: HardhatRuntimeEnvironment) => { if (!hre.network.zksync) { return; @@ -393,7 +446,7 @@ subtask( if (compiler && compiler.eraVersion) { const compilersCache = await getCompilersDir(); - let path: string = ''; + let compilerPath: string = ''; let version: string = ''; let normalizedVersion: string = ''; @@ -409,7 +462,7 @@ subtask( await zkVmSolcCompilerDownloader.downloadCompiler(); } - path = zkVmSolcCompilerDownloader.getCompilerPath(); + compilerPath = zkVmSolcCompilerDownloader.getCompilerPath(); version = zkVmSolcCompilerDownloader.getVersion(); normalizedVersion = getZkVmNormalizedVersion( zkVmSolcCompilerDownloader.getSolcVersion(), @@ -419,7 +472,7 @@ subtask( console.info(chalk.yellow(COMPILING_INFO_MESSAGE_ZKVM_SOLC(hre.config.zksolc.version, version))); return { - compilerPath: path, + compilerPath, isSolcJs: false, version, longVersion: normalizedVersion, diff --git a/packages/hardhat-zksync-solc/src/types.ts b/packages/hardhat-zksync-solc/src/types.ts index db0ccb406..8bd06822b 100644 --- a/packages/hardhat-zksync-solc/src/types.ts +++ b/packages/hardhat-zksync-solc/src/types.ts @@ -12,6 +12,8 @@ export interface ZkSolcConfig { missingLibrariesPath?: string; // Whether there are missing libraries. This is used as a temp flag that will enable or disable logs for successful compilation. areLibrariesMissing?: boolean; + // Flag to enable or disable library linking. By default, it is disabled. + linkLibraries?: LinkLibraries; // Optimizer settings optimizer?: { enabled?: boolean; @@ -73,3 +75,10 @@ export interface MissingLibrary { contractPath: string; missingLibraries: string[]; } + +export interface LinkLibraries { + contractZbinPath: string; + libraries?: { + [libraryName: string]: string; + }; +} diff --git a/packages/hardhat-zksync-solc/src/utils.ts b/packages/hardhat-zksync-solc/src/utils.ts index f2f95c80d..e4589219a 100644 --- a/packages/hardhat-zksync-solc/src/utils.ts +++ b/packages/hardhat-zksync-solc/src/utils.ts @@ -1,6 +1,6 @@ import semver from 'semver'; import crypto from 'crypto'; -import { SolcUserConfig } from 'hardhat/types'; +import { Artifact, HardhatRuntimeEnvironment, SolcUserConfig } from 'hardhat/types'; import fse from 'fs-extra'; import lockfile from 'proper-lockfile'; import fs from 'fs'; @@ -394,3 +394,44 @@ export function getZkVmNormalizedVersion(solcVersion: string, zkVmSolcVersion: s export async function getLatestEraVersion(): Promise { return (await getLatestRelease(ZKSOLC_BIN_OWNER, ZKVM_SOLC_BIN_REPOSITORY_NAME, USER_AGENT, '')).split('-')[1]; } + +export function generateFQN(sourceName: string, contractName: string): string { + return `${sourceName}:${contractName}`; +} + +export async function getLibraryLink( + hre: HardhatRuntimeEnvironment, + libraries: { [contractName: string]: string }, + contractZbinPath: string, +) { + if (libraries === undefined || Object.keys(libraries).length === 0) { + return { + contractZbinPath, + }; + } + + const populatedLibraries: { [contractName: string]: string } = {}; + + 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] as string; + }), + ); + + return { + contractZbinPath, + libraries: populatedLibraries, + }; +} + +export async function replaceArtifactBytecodeAndSave( + hre: HardhatRuntimeEnvironment, + artifact: Artifact, + contractFilePath: string, +) { + const newBytecode = fs.readFileSync(contractFilePath, { encoding: 'utf-8' }); + artifact.bytecode = newBytecode; + artifact.deployedBytecode = newBytecode; + await hre.artifacts.saveArtifactAndDebugFile(artifact); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3825ffbef..3999717b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1148,6 +1148,9 @@ importers: fs-extra: specifier: ^11.2.0 version: 11.2.0 + lodash: + specifier: ^4.17.21 + version: 4.17.21 proper-lockfile: specifier: ^4.1.2 version: 4.1.2 @@ -1176,6 +1179,9 @@ importers: '@types/fs-extra': specifier: ^11.0.4 version: 11.0.4 + '@types/lodash': + specifier: ^4.14.202 + version: 4.17.4 '@types/mocha': specifier: ^10.0.6 version: 10.0.6 From ef2fc25cf859f8182080a59744159b20f2406e76 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Mon, 16 Sep 2024 13:36:40 +0200 Subject: [PATCH 2/5] fix: update build info and refresh artifact after library linking --- .../deploy/001_deploy.ts | 3 +-- .../hardhat.config.ts | 2 +- .../hardhat-zksync-deploy/src/deployer-helper.ts | 13 +++++++++++-- packages/hardhat-zksync-solc/src/index.ts | 16 ++++++++-------- packages/hardhat-zksync-solc/src/utils.ts | 9 +++++++++ 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/examples/noninline-libraries-example/deploy/001_deploy.ts b/examples/noninline-libraries-example/deploy/001_deploy.ts index 13c0b97b1..d343233e7 100644 --- a/examples/noninline-libraries-example/deploy/001_deploy.ts +++ b/examples/noninline-libraries-example/deploy/001_deploy.ts @@ -18,8 +18,7 @@ export default async function (hre: HardhatRuntimeEnvironment) { // Deploy this contract. The returned object will be of a `Contract` type, similarly to ones in `ethers`. // `greeting` is an argument for contract constructor. - const greeterContract = await deployer.deploy(artifact, [], 'create', { - }); + const greeterContract = await deployer.deploy(artifact, []); // Show the contract info. const contractAddress = await greeterContract.getAddress(); diff --git a/examples/noninline-libraries-example/hardhat.config.ts b/examples/noninline-libraries-example/hardhat.config.ts index 4e92b844f..212dacf92 100644 --- a/examples/noninline-libraries-example/hardhat.config.ts +++ b/examples/noninline-libraries-example/hardhat.config.ts @@ -14,7 +14,7 @@ const config: HardhatUserConfig = { url: 'http://0.0.0.0:8545', }, zkSyncNetwork: { - url: 'http://0.0.0.0:8011', + url: 'http://0.0.0.0:3050', ethNetwork: 'ethNetwork', zksync: true, }, diff --git a/packages/hardhat-zksync-deploy/src/deployer-helper.ts b/packages/hardhat-zksync-deploy/src/deployer-helper.ts index 3fc2da9f1..2b45a3bfb 100644 --- a/packages/hardhat-zksync-deploy/src/deployer-helper.ts +++ b/packages/hardhat-zksync-deploy/src/deployer-helper.ts @@ -2,6 +2,8 @@ import { HardhatRuntimeEnvironment, HttpNetworkConfig, Network, NetworksConfig } import { DeploymentType } from 'zksync-ethers/build/types'; import * as zk from 'zksync-ethers'; import * as ethers from 'ethers'; +import { TASK_COMPILE_LINK } from '@matterlabs/hardhat-zksync-solc/dist/src/constants'; +import { generateFQN } from '@matterlabs/hardhat-zksync-solc/dist/src/utils'; import { ZkSyncArtifact, ZkSyncOverrides } from './types'; import { ZkSyncDeployPluginError } from './errors'; import { isHttpNetworkConfig, isValidEthNetworkURL } from './utils'; @@ -72,7 +74,10 @@ export async function deploy( const artifact: ZkSyncArtifact = typeof contractNameOrArtifact === 'string' ? await loadArtifact(hre, contractNameOrArtifact) - : contractNameOrArtifact; + : await loadArtifact( + hre, + generateFQN(contractNameOrArtifact.sourceName, contractNameOrArtifact.contractName), + ); const baseDeps = await _extractFactoryDeps(hre, artifact); const additionalDeps = additionalFactoryDeps ? additionalFactoryDeps.map((val) => ethers.hexlify(val)) : []; @@ -214,7 +219,11 @@ export async function linkLibrariesIfNeeded( typeof contractNameOrArtifact === 'string' ? await loadArtifact(hre, contractNameOrArtifact) : contractNameOrArtifact; - await hre.run('compile:link', { sourceName: artifact.sourceName, contractName: artifact.contractName, libraries }); + await hre.run(TASK_COMPILE_LINK, { + sourceName: artifact.sourceName, + contractName: artifact.contractName, + libraries, + }); } export function createProviders( diff --git a/packages/hardhat-zksync-solc/src/index.ts b/packages/hardhat-zksync-solc/src/index.ts index ca0daf1e6..bb4d3e68d 100644 --- a/packages/hardhat-zksync-solc/src/index.ts +++ b/packages/hardhat-zksync-solc/src/index.ts @@ -142,8 +142,8 @@ task(TASK_COMPILE).setAction( ); subtask(TASK_COMPILE_LINK) - .addParam('sourceName', 'Source name') - .addParam('contractName', 'ContractName') + .addParam('sourceName', 'Source name of the artifact') + .addParam('contractName', 'Contract name of the artifact') .addOptionalParam('libraries', undefined, undefined, types.any) .setAction(async ({ sourceName, contractName, libraries }, hre: HardhatRuntimeEnvironment) => { if (!hre.network.zksync) { @@ -154,21 +154,21 @@ subtask(TASK_COMPILE_LINK) await hre.run(TASK_UPDATE_SOLIDITY_COMPILERS); if (semver.lt(hre.config.zksolc.version, ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING)) { - throw new ZkSyncSolcPluginError( - `Linking libraries is only supported for zksolc compiler version ${ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING} or higher`, - ); + return undefined; } + const contractFQN = generateFQN(sourceName, contractName); const contractFilePath = path.join(hre.config.paths.artifacts, sourceName, `${contractName}.zbin`); const artifact = await hre.artifacts.readArtifact(contractFQN); fs.writeFileSync(contractFilePath, artifact.bytecode); - const output = await link(hre.config.zksolc, await getLibraryLink(hre, libraries, contractFilePath)); if (!lodash.isEmpty(output.unlinked)) { throw new ZkSyncSolcPluginError( - `Libraries for contract ${contractFQN} are not linked: ${Object.values(output.unlinked[contractFQN]) + `Libraries for contract ${contractFQN} are not linked: ${Object.values( + output.unlinked[contractFilePath], + ) .map((lib) => `${lib}`) .join(', ')}`, ); @@ -176,7 +176,7 @@ subtask(TASK_COMPILE_LINK) if (!lodash.isEmpty(output.ignored)) { console.warn( - `Linking of libraries for contract ${contractFQN} are ignored, delete artifacts and cache folders and try again with new library addresses`, + `Linking of some libraries for contract ${contractFQN} are ignored, delete artifacts and cache folders and try again with new library addresses if that was intended`, ); } diff --git a/packages/hardhat-zksync-solc/src/utils.ts b/packages/hardhat-zksync-solc/src/utils.ts index e4589219a..a5aa8c9a3 100644 --- a/packages/hardhat-zksync-solc/src/utils.ts +++ b/packages/hardhat-zksync-solc/src/utils.ts @@ -8,6 +8,7 @@ import path from 'path'; import util from 'util'; import type { Dispatcher } from 'undici'; import chalk from 'chalk'; +import { BUILD_INFO_DIR_NAME } from 'hardhat/internal/constants'; import { CompilerVersionInfo } from './compile/downloader'; import { CompilerOutputSelection, MissingLibrary, ZkSolcConfig } from './types'; import { @@ -433,5 +434,13 @@ export async function replaceArtifactBytecodeAndSave( const newBytecode = fs.readFileSync(contractFilePath, { encoding: 'utf-8' }); artifact.bytecode = newBytecode; artifact.deployedBytecode = newBytecode; + + const buildInfo = await hre.artifacts.getBuildInfo(generateFQN(artifact.sourceName, artifact.contractName)); + if (buildInfo) { + buildInfo.output.contracts[artifact.sourceName][artifact.contractName].evm.bytecode.object = newBytecode; + const buildInfoPath = path.join(hre.config.paths.artifacts, BUILD_INFO_DIR_NAME, `${buildInfo?.id}.json`); + await fse.writeJSON(buildInfoPath, buildInfo); + } + await hre.artifacts.saveArtifactAndDebugFile(artifact); } From 0b94956c9d195abca6cda21444fdd7c6da832a44 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Mon, 16 Sep 2024 14:49:16 +0200 Subject: [PATCH 3/5] fix: add compile link subtask to the vyper --- packages/hardhat-zksync-vyper/src/constants.ts | 3 ++- packages/hardhat-zksync-vyper/src/index.ts | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/hardhat-zksync-vyper/src/constants.ts b/packages/hardhat-zksync-vyper/src/constants.ts index 7d49ee439..6e3e1c2fd 100644 --- a/packages/hardhat-zksync-vyper/src/constants.ts +++ b/packages/hardhat-zksync-vyper/src/constants.ts @@ -8,11 +8,12 @@ export const TASK_COMPILE_VYPER_CHECK_ERRORS = 'compile:vyper:check-errors'; export const TASK_COMPILE_VYPER_LOG_COMPILATION_ERRORS = 'compile:vyper:log:compilation-errors'; export const ZKVYPER_COMPILER_PATH_VERSION = 'local_or_remote'; - // User agent of MacOSX Chrome 120.0.0.0 export const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; +export const TASK_COMPILE_LINK: string = 'compile:link'; + export const defaultZkVyperConfig: ZkVyperConfig = { version: 'latest', compilerSource: 'binary', diff --git a/packages/hardhat-zksync-vyper/src/index.ts b/packages/hardhat-zksync-vyper/src/index.ts index 369f2df35..76d754945 100644 --- a/packages/hardhat-zksync-vyper/src/index.ts +++ b/packages/hardhat-zksync-vyper/src/index.ts @@ -13,18 +13,20 @@ import { getCompilersDir } from 'hardhat/internal/util/global-dir'; import { Mutex } from 'hardhat/internal/vendor/await-semaphore'; import './type-extensions'; import chalk from 'chalk'; -import { CompilationJob } from 'hardhat/types'; +import { CompilationJob, HardhatRuntimeEnvironment } from 'hardhat/types'; import { PROXY_NAME, ProxtContractOutput, ZkArtifacts, proxyNames } from './artifacts'; import { compile } from './compile'; import { checkSupportedVyperVersions, pluralize } from './utils'; import { COMPILING_INFO_MESSAGE, defaultZkVyperConfig, + TASK_COMPILE_LINK, TASK_COMPILE_VYPER_CHECK_ERRORS, TASK_COMPILE_VYPER_LOG_COMPILATION_ERRORS, ZKVYPER_COMPILER_PATH_VERSION, } from './constants'; import { ZkVyperCompilerDownloader } from './compile/downloader'; +import { ZkSyncVyperPluginError } from './errors'; extendConfig((config, userConfig) => { defaultZkVyperConfig.version = userConfig.zkvyper?.settings?.compilerPath @@ -61,6 +63,19 @@ extendEnvironment((hre) => { } }); +subtask(TASK_COMPILE_LINK) + .addParam('sourceName', 'Source name of the artifact') + .addParam('contractName', 'Contract name of the artifact') + .addOptionalParam('libraries', undefined, undefined, types.any) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .setAction(async ({ sourceName, contractName, libraries }, hre: HardhatRuntimeEnvironment) => { + if (!hre.network.zksync) { + throw new ZkSyncVyperPluginError('This task is only available for zkSync network'); + } + // Libraries are not supported for vyper + return undefined; + }); + // If there're no .sol files to compile - that's ok. subtask(TASK_COMPILE_SOLIDITY_LOG_NOTHING_TO_COMPILE, async () => {}); From 2ef9fcbbfc5960f8cf0239dc84afa98a48ff1df2 Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Wed, 25 Sep 2024 20:17:00 +0200 Subject: [PATCH 4/5] fix: add tests and adjust proccess for different networks --- .../src/deployer-helper.ts | 10 +- .../src/extension-generator.ts | 4 +- packages/hardhat-zksync-ethers/src/helpers.ts | 32 +++- packages/hardhat-zksync-ethers/src/types.ts | 10 +- packages/hardhat-zksync-ethers/src/utils.ts | 20 +- packages/hardhat-zksync-solc/src/index.ts | 51 +---- packages/hardhat-zksync-solc/src/plugin.ts | 62 ++++++ packages/hardhat-zksync-solc/src/utils.ts | 24 +-- .../test/tests/compile-link.test.ts | 179 ++++++++++++++++++ 9 files changed, 306 insertions(+), 86 deletions(-) create mode 100644 packages/hardhat-zksync-solc/src/plugin.ts create mode 100644 packages/hardhat-zksync-solc/test/tests/compile-link.test.ts diff --git a/packages/hardhat-zksync-deploy/src/deployer-helper.ts b/packages/hardhat-zksync-deploy/src/deployer-helper.ts index 2b45a3bfb..77a61ff3f 100644 --- a/packages/hardhat-zksync-deploy/src/deployer-helper.ts +++ b/packages/hardhat-zksync-deploy/src/deployer-helper.ts @@ -69,7 +69,7 @@ export async function deploy( overrides?: ZkSyncOverrides, additionalFactoryDeps?: ethers.BytesLike[], ): Promise { - await linkLibrariesIfNeeded(hre, contractNameOrArtifact, overrides?.libraries); + const linkedBytecode = await linkLibrariesIfNeeded(hre, contractNameOrArtifact, overrides?.libraries); const artifact: ZkSyncArtifact = typeof contractNameOrArtifact === 'string' @@ -79,6 +79,11 @@ export async function deploy( generateFQN(contractNameOrArtifact.sourceName, contractNameOrArtifact.contractName), ); + if (linkedBytecode) { + artifact.bytecode = linkedBytecode; + artifact.deployedBytecode = linkedBytecode; + } + const baseDeps = await _extractFactoryDeps(hre, artifact); const additionalDeps = additionalFactoryDeps ? additionalFactoryDeps.map((val) => ethers.hexlify(val)) : []; const factoryDeps = [...baseDeps, ...additionalDeps]; @@ -219,7 +224,8 @@ export async function linkLibrariesIfNeeded( typeof contractNameOrArtifact === 'string' ? await loadArtifact(hre, contractNameOrArtifact) : contractNameOrArtifact; - await hre.run(TASK_COMPILE_LINK, { + + return await hre.run(TASK_COMPILE_LINK, { sourceName: artifact.sourceName, contractName: artifact.contractName, libraries, diff --git a/packages/hardhat-zksync-ethers/src/extension-generator.ts b/packages/hardhat-zksync-ethers/src/extension-generator.ts index 440815740..fa8a3354b 100644 --- a/packages/hardhat-zksync-ethers/src/extension-generator.ts +++ b/packages/hardhat-zksync-ethers/src/extension-generator.ts @@ -57,7 +57,8 @@ export class ZkSyncGenerator implements Generator { deployContract: ( artifact: ZkSyncArtifact, constructorArguments: any[], - walletOrSigner?: HardhatZksyncSignerOrWallet, + walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, overrides?: Overrides, additionalFactoryDeps?: BytesLike[], ) => @@ -66,6 +67,7 @@ export class ZkSyncGenerator implements Generator { artifact, constructorArguments, walletOrSigner, + deploymentType, overrides, additionalFactoryDeps, ), diff --git a/packages/hardhat-zksync-ethers/src/helpers.ts b/packages/hardhat-zksync-ethers/src/helpers.ts index efd1d1dfc..beeb75340 100644 --- a/packages/hardhat-zksync-ethers/src/helpers.ts +++ b/packages/hardhat-zksync-ethers/src/helpers.ts @@ -19,7 +19,16 @@ import { ZkSyncArtifact, } from './types'; import { ZkSyncEthersPluginError } from './errors'; -import { getSignerAccounts, getSignerOrWallet, getWalletsFromAccount, isArtifact, isNumber, isString } from './utils'; +import { + getSignerAccounts, + getSignerOrWallet, + getWalletsFromAccount, + isArtifact, + isFactoryOptions, + isNumber, + isString, + linkLibrariesIfNeeded, +} from './utils'; import { ZKSOLC_ARTIFACT_FORMAT_VERSION, ZKVYPER_ARTIFACT_FORMAT_VERSION } from './constants'; import { HardhatZksyncSigner } from './signers/hardhat-zksync-signer'; @@ -129,11 +138,19 @@ If you want to call a contract using ${artifact.contractName} as its interface u ); } + const libraries: { [libraryName: string]: string } | undefined = isFactoryOptions(walletOrSignerOrOptions) + ? walletOrSignerOrOptions.libraries + : undefined; + + const linkedBytecode = await linkLibrariesIfNeeded(hre, artifact, libraries); + + const walletOrSigner: HardhatZksyncSignerOrWallet | undefined = getSignerOrWallet(walletOrSignerOrOptions); + return getContractFactoryByAbiAndBytecode( hre, artifact.abi, - artifact.bytecode, - walletOrSignerOrOptions, + linkedBytecode ?? artifact.bytecode, + walletOrSigner, deploymentType, ); } @@ -142,11 +159,9 @@ async function getContractFactoryByAbiAndBytecode> { - let walletOrSigner: HardhatZksyncSignerOrWallet | undefined = getSignerOrWallet(walletOrSignerOrOptions); - if (!walletOrSigner) { walletOrSigner = (await getSigners(hre))[0]; } @@ -208,7 +223,8 @@ export async function deployContract( hre: HardhatRuntimeEnvironment, artifactOrContract: ZkSyncArtifact | string, constructorArguments: any[] = [], - walletOrSigner?: HardhatZksyncSignerOrWallet, + walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, overrides?: ethers.Overrides, additionalFactoryDeps?: ethers.BytesLike[], ): Promise { @@ -219,7 +235,7 @@ export async function deployContract( const artifact = typeof artifactOrContract === 'string' ? await loadArtifact(hre, artifactOrContract) : artifactOrContract; - const factory = await getContractFactoryFromArtifact(hre, artifact, walletOrSigner); + const factory = await getContractFactoryFromArtifact(hre, artifact, walletOrSigner, deploymentType); const baseDeps = await extractFactoryDeps(hre, artifact); const additionalDeps = additionalFactoryDeps ? additionalFactoryDeps.map((val) => ethers.hexlify(val)) : []; diff --git a/packages/hardhat-zksync-ethers/src/types.ts b/packages/hardhat-zksync-ethers/src/types.ts index 8dbe5c43e..2a0a97baa 100644 --- a/packages/hardhat-zksync-ethers/src/types.ts +++ b/packages/hardhat-zksync-ethers/src/types.ts @@ -21,6 +21,7 @@ export interface ZkSyncArtifact extends Artifact { export interface ZkFactoryOptions { wallet?: Wallet; signer?: HardhatZksyncSigner; + libraries?: { [libraryName: string]: string }; } export type HardhatZksyncSignerOrWallet = Wallet | HardhatZksyncSigner; @@ -28,20 +29,20 @@ export type HardhatZksyncSignerOrWalletOrFactoryOptions = HardhatZksyncSignerOrW export type GetContractFactoryArtifactName = ( name: string, - walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + walletOrSignerOrOptions?: HardhatZksyncSignerOrWalletOrFactoryOptions, deploymentType?: DeploymentType, ) => Promise>; export type GetContractFactoryAbiBytecode = ( abi: any[], bytecode: ethers.BytesLike, - walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + walletOrSigner?: HardhatZksyncSignerOrWallet, deploymentType?: DeploymentType, ) => Promise>; export type GetContractFactoryArtifact = ( artifact: ZkSyncArtifact, - walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + walletOrSignerOrOptions?: HardhatZksyncSignerOrWalletOrFactoryOptions, deploymentType?: DeploymentType, ) => Promise>; @@ -104,7 +105,8 @@ export interface HardhatZksyncEthersHelpers { deployContract: ( artifact: ZkSyncArtifact | string, constructorArguments: any[], - walletOrSigner?: HardhatZksyncSignerOrWallet, + walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, overrides?: ethers.Overrides, additionalFactoryDeps?: ethers.BytesLike[], ) => Promise; diff --git a/packages/hardhat-zksync-ethers/src/utils.ts b/packages/hardhat-zksync-ethers/src/utils.ts index 5fb7bc587..1c8fb6cf3 100644 --- a/packages/hardhat-zksync-ethers/src/utils.ts +++ b/packages/hardhat-zksync-ethers/src/utils.ts @@ -9,6 +9,7 @@ import { import { Provider, Signer, Wallet } from 'zksync-ethers'; import { ethers } from 'ethers'; import { isAddressEq } from 'zksync-ethers/build/utils'; +import { TASK_COMPILE_LINK } from '@matterlabs/hardhat-zksync-solc/dist/src/constants'; import { HardhatZksyncSignerOrWallet, HardhatZksyncSignerOrWalletOrFactoryOptions, @@ -26,7 +27,7 @@ import { richWallets } from './rich-wallets'; import { ZkSyncEthersPluginError } from './errors'; import { HardhatZksyncEthersProvider } from './hardhat-zksync-provider'; import { HardhatZksyncSigner } from './signers/hardhat-zksync-signer'; -import { getWallets } from './helpers'; +import { getWallets, loadArtifact } from './helpers'; export function isHardhatNetworkHDAccountsConfig(object: any): object is HardhatNetworkHDAccountsConfig { return 'mnemonic' in object; @@ -262,3 +263,20 @@ export async function isImpersonatedSigner(provider: Provider, address: string): await provider.send('hardhat_impersonateAccount', [address]); return true; } + +export async function linkLibrariesIfNeeded( + hre: HardhatRuntimeEnvironment, + contractNameOrArtifact: ZkSyncArtifact | string, + libraries?: { [libraryName: string]: string }, +) { + const artifact: ZkSyncArtifact = + typeof contractNameOrArtifact === 'string' + ? await loadArtifact(hre, contractNameOrArtifact) + : contractNameOrArtifact; + + return await hre.run(TASK_COMPILE_LINK, { + sourceName: artifact.sourceName, + contractName: artifact.contractName, + libraries, + }); +} diff --git a/packages/hardhat-zksync-solc/src/index.ts b/packages/hardhat-zksync-solc/src/index.ts index 53568d725..6dc8eb4dd 100644 --- a/packages/hardhat-zksync-solc/src/index.ts +++ b/packages/hardhat-zksync-solc/src/index.ts @@ -33,10 +33,7 @@ import { TaskArguments, } from 'hardhat/types'; import debug from 'debug'; -import semver from 'semver'; -import lodash from 'lodash'; -import path from 'path'; -import { compile, link } from './compile'; +import { compile } from './compile'; import { zeroxlify, getZksolcUrl, @@ -47,9 +44,6 @@ import { getZkVmNormalizedVersion, updateBreakableCompilerConfig, getLatestEraVersion, - getLibraryLink, - generateFQN, - replaceArtifactBytecodeAndSave, } from './utils'; import { defaultZkSolcConfig, @@ -64,7 +58,6 @@ import { TASK_UPDATE_SOLIDITY_COMPILERS, TASK_DOWNLOAD_ZKSOLC, TASK_COMPILE_LINK, - ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING, } from './constants'; import { ZksolcCompilerDownloader } from './compile/downloader'; import { ZkVmSolcCompilerDownloader } from './compile/zkvm-solc-downloader'; @@ -74,8 +67,8 @@ import { SolcStringUserConfigExtractor, SolcUserConfigExtractor, } from './config-extractor'; -import { ZkSyncSolcPluginError } from './errors'; import { FactoryDeps, ZkSyncCompilerInput } from './types'; +import { compileLink } from './plugin'; const logDebug = debug('hardhat:core:tasks:compile'); @@ -146,45 +139,7 @@ subtask(TASK_COMPILE_LINK) .addParam('sourceName', 'Source name of the artifact') .addParam('contractName', 'Contract name of the artifact') .addOptionalParam('libraries', undefined, undefined, types.any) - .setAction(async ({ sourceName, contractName, libraries }, hre: HardhatRuntimeEnvironment) => { - if (!hre.network.zksync) { - throw new ZkSyncSolcPluginError('This task is only available for zkSync network'); - } - - await hre.run(TASK_DOWNLOAD_ZKSOLC); - await hre.run(TASK_UPDATE_SOLIDITY_COMPILERS); - - if (semver.lt(hre.config.zksolc.version, ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING)) { - return undefined; - } - - const contractFQN = generateFQN(sourceName, contractName); - const contractFilePath = path.join(hre.config.paths.artifacts, sourceName, `${contractName}.zbin`); - const artifact = await hre.artifacts.readArtifact(contractFQN); - - fs.writeFileSync(contractFilePath, artifact.bytecode); - const output = await link(hre.config.zksolc, await getLibraryLink(hre, libraries, contractFilePath)); - - if (!lodash.isEmpty(output.unlinked)) { - throw new ZkSyncSolcPluginError( - `Libraries for contract ${contractFQN} are not linked: ${Object.values( - output.unlinked[contractFilePath], - ) - .map((lib) => `${lib}`) - .join(', ')}`, - ); - } - - if (!lodash.isEmpty(output.ignored)) { - console.warn( - `Linking of some libraries for contract ${contractFQN} are ignored, delete artifacts and cache folders and try again with new library addresses if that was intended`, - ); - } - - await replaceArtifactBytecodeAndSave(hre, artifact, contractFilePath); - - return output; - }); + .setAction(compileLink); subtask(TASK_DOWNLOAD_ZKSOLC, async (_args: any, hre: HardhatRuntimeEnvironment) => { if (!hre.network.zksync) { diff --git a/packages/hardhat-zksync-solc/src/plugin.ts b/packages/hardhat-zksync-solc/src/plugin.ts new file mode 100644 index 000000000..12c923d77 --- /dev/null +++ b/packages/hardhat-zksync-solc/src/plugin.ts @@ -0,0 +1,62 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import path from 'path'; +import fs from 'fs'; +import lodash from 'lodash'; +import semver from 'semver'; +import { + TASK_DOWNLOAD_ZKSOLC, + TASK_UPDATE_SOLIDITY_COMPILERS, + ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING, +} from './constants'; +import { ZkSyncSolcPluginError } from './errors'; +import { generateFQN, getLibraryLink } from './utils'; +import { link } from './compile'; + +export async function compileLink( + taskArgs: { + sourceName: string; + contractName: string; + libraries?: { [libraryName: string]: string }; + }, + hre: HardhatRuntimeEnvironment, +): Promise { + if (!hre.network.zksync) { + throw new ZkSyncSolcPluginError('This task is only available for zkSync network'); + } + + await hre.run(TASK_DOWNLOAD_ZKSOLC); + await hre.run(TASK_UPDATE_SOLIDITY_COMPILERS); + + if (semver.lt(hre.config.zksolc.version, ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING)) { + return undefined; + } + + const contractFQN = generateFQN(taskArgs.sourceName, taskArgs.contractName); + const contractFilePath = path.join( + hre.config.paths.artifacts, + taskArgs.sourceName, + `${taskArgs.contractName}.zbin`, + ); + const artifact = await hre.artifacts.readArtifact(contractFQN); + + fs.writeFileSync(contractFilePath, artifact.bytecode); + const output = await link(hre.config.zksolc, await getLibraryLink(hre, taskArgs.libraries, contractFilePath)); + + if (!lodash.isEmpty(output.unlinked)) { + throw new ZkSyncSolcPluginError( + `Libraries for contract ${contractFQN} are not linked: ${Object.values(output.unlinked[contractFilePath]) + .map((lib) => `${lib}`) + .join(', ')}`, + ); + } + + if (!lodash.isEmpty(output.ignored)) { + console.warn( + `Linking of some libraries for contract ${contractFQN} are ignored as they are provided as the duplicate of the already linked libraries.`, + ); + } + + const newBytecode = fs.readFileSync(contractFilePath, { encoding: 'utf-8' }); + + return newBytecode; +} diff --git a/packages/hardhat-zksync-solc/src/utils.ts b/packages/hardhat-zksync-solc/src/utils.ts index 0d253dd11..7382bad61 100644 --- a/packages/hardhat-zksync-solc/src/utils.ts +++ b/packages/hardhat-zksync-solc/src/utils.ts @@ -1,6 +1,6 @@ import semver from 'semver'; import crypto from 'crypto'; -import { Artifact, HardhatRuntimeEnvironment, SolcUserConfig } from 'hardhat/types'; +import { HardhatRuntimeEnvironment, SolcUserConfig } from 'hardhat/types'; import fse from 'fs-extra'; import lockfile from 'proper-lockfile'; import fs from 'fs'; @@ -8,7 +8,6 @@ import path from 'path'; import util from 'util'; import type { Dispatcher } from 'undici'; import chalk from 'chalk'; -import { BUILD_INFO_DIR_NAME } from 'hardhat/internal/constants'; import { CompilerVersionInfo } from './compile/downloader'; import { CompilerOutputSelection, MissingLibrary, ZkSolcConfig } from './types'; import { @@ -422,7 +421,7 @@ export function generateFQN(sourceName: string, contractName: string): string { export async function getLibraryLink( hre: HardhatRuntimeEnvironment, - libraries: { [contractName: string]: string }, + libraries: { [contractName: string]: string } | undefined, contractZbinPath: string, ) { if (libraries === undefined || Object.keys(libraries).length === 0) { @@ -445,22 +444,3 @@ export async function getLibraryLink( libraries: populatedLibraries, }; } - -export async function replaceArtifactBytecodeAndSave( - hre: HardhatRuntimeEnvironment, - artifact: Artifact, - contractFilePath: string, -) { - const newBytecode = fs.readFileSync(contractFilePath, { encoding: 'utf-8' }); - artifact.bytecode = newBytecode; - artifact.deployedBytecode = newBytecode; - - const buildInfo = await hre.artifacts.getBuildInfo(generateFQN(artifact.sourceName, artifact.contractName)); - if (buildInfo) { - buildInfo.output.contracts[artifact.sourceName][artifact.contractName].evm.bytecode.object = newBytecode; - const buildInfoPath = path.join(hre.config.paths.artifacts, BUILD_INFO_DIR_NAME, `${buildInfo?.id}.json`); - await fse.writeJSON(buildInfoPath, buildInfo); - } - - await hre.artifacts.saveArtifactAndDebugFile(artifact); -} diff --git a/packages/hardhat-zksync-solc/test/tests/compile-link.test.ts b/packages/hardhat-zksync-solc/test/tests/compile-link.test.ts new file mode 100644 index 000000000..ff65da648 --- /dev/null +++ b/packages/hardhat-zksync-solc/test/tests/compile-link.test.ts @@ -0,0 +1,179 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; + +import fs from 'fs'; +import path from 'path'; +import semver from 'semver'; +import { TASK_DOWNLOAD_ZKSOLC, TASK_UPDATE_SOLIDITY_COMPILERS } from '../../src/constants'; +import * as utils from '../../src/utils'; +import * as compile from '../../src/compile/index'; +import { compileLink } from '../../src/plugin'; +import { getLibraryLink } from '../../src/utils'; + +describe('compile link', () => { + let hre: any; + before(() => { + hre = { + network: { zksync: true }, + config: { + zksolc: { version: '1.0.0' }, + paths: { artifacts: 'artifacts' }, + }, + artifacts: { + readArtifact: sinon.stub().resolves({ bytecode: '0x' }), + }, + run: sinon.stub().resolves(), + } as unknown as any; + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should throw an error if not on zkSync network', async () => { + hre.network.zksync = false; + + try { + await compileLink({ sourceName: 'Test', contractName: 'Test' }, hre); + } catch (error: any) { + expect(error.message).to.equal('This task is only available for zkSync network'); + } + }); + + it('should download zksolc and update solidity compilers', async () => { + hre.network.zksync = true; + + await compileLink({ sourceName: 'Test', contractName: 'Test' }, hre); + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(hre.run.calledWith(TASK_DOWNLOAD_ZKSOLC)).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(hre.run.calledWith(TASK_UPDATE_SOLIDITY_COMPILERS)).to.be.true; + }); + + it('should return undefined if zksolc version is less than required', async () => { + hre.config.zksolc.version = '0.1.0'; + hre.network.zksync = true; + + const result = await compileLink({ sourceName: 'Test', contractName: 'Test' }, hre); + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(result).to.be.undefined; + }); + + it('should link libraries and return new bytecode', async () => { + hre.network.zksync = true; + sinon.stub(semver, 'lt').returns(false); + sinon.stub(utils, 'generateFQN').returns('Test:Test'); + sinon.stub(path, 'join').returns('artifacts/Test/Test.zbin'); + sinon.stub(fs, 'writeFileSync'); + sinon.stub(fs, 'readFileSync').returns('0xnewbytecode'); + sinon.stub(utils, 'getLibraryLink').resolves({ + contractZbinPath: 'artifacts/Test/Test.zbin', + }); + sinon.stub(compile, 'link').resolves({ unlinked: {}, ignored: {} }); + + const result = await compileLink({ sourceName: 'Test', contractName: 'Test' }, hre); + + expect(result).to.equal('0xnewbytecode'); + }); + + it('should throw an error if libraries are not linked', async () => { + hre.network.zksync = true; + sinon.stub(semver, 'lt').returns(false); + sinon.stub(utils, 'generateFQN').returns('Test:Test'); + sinon.stub(path, 'join').returns('artifacts/Test/Test.zbin'); + sinon.stub(fs, 'writeFileSync'); + sinon.stub(utils, 'getLibraryLink').resolves({ + contractZbinPath: 'artifacts/Test/Test.zbin', + }); + sinon.stub(compile, 'link').resolves({ unlinked: { 'artifacts/Test/Test.zbin': ['Lib'] }, ignored: {} }); + + try { + await compileLink({ sourceName: 'Test', contractName: 'Test' }, hre); + } catch (error: any) { + expect(error.message).to.include('Libraries for contract Test:Test are not linked'); + } + }); + + it('should warn if some libraries are ignored', async () => { + hre.network.zksync = true; + const consoleWarnStub = sinon.stub(console, 'warn'); + sinon.stub(semver, 'lt').returns(false); + sinon.stub(utils, 'generateFQN').returns('Test:Test'); + sinon.stub(path, 'join').returns('artifacts/Test/Test.zbin'); + sinon.stub(fs, 'writeFileSync'); + sinon.stub(fs, 'readFileSync').returns('0xnewbytecode'); + sinon.stub(utils, 'getLibraryLink').resolves({ + contractZbinPath: 'artifacts/Test/Test.zbin', + }); + sinon.stub(compile, 'link').resolves({ unlinked: {}, ignored: { 'artifacts/Test/Test.zbin': ['Lib'] } }); + + await compileLink({ sourceName: 'Test', contractName: 'Test' }, hre); + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(consoleWarnStub.calledWith(sinon.match.string)).to.be.true; + }); +}); + +describe('getLibraryLink', () => { + let hre: any; + + beforeEach(() => { + hre = { + artifacts: { + readArtifact: sinon.stub(), + }, + }; + }); + + it('should return only contractZbinPath when libraries is undefined', async () => { + const result = await getLibraryLink(hre, undefined, 'path/to/contract.zbin'); + expect(result).to.deep.equal({ contractZbinPath: 'path/to/contract.zbin' }); + }); + + it('should return only contractZbinPath when libraries is empty', async () => { + const result = await getLibraryLink(hre, {}, 'path/to/contract.zbin'); + expect(result).to.deep.equal({ contractZbinPath: 'path/to/contract.zbin' }); + }); + + it('should populate libraries correctly', async () => { + hre.artifacts.readArtifact + .withArgs('Lib1') + .resolves({ sourceName: 'contracts/Lib1.sol', contractName: 'Lib1' }); + hre.artifacts.readArtifact + .withArgs('Lib2') + .resolves({ sourceName: 'contracts/Lib2.sol', contractName: 'Lib2' }); + + const libraries = { + Lib1: '0x1234567890123456789012345678901234567890', + Lib2: '0x0987654321098765432109876543210987654321', + }; + + const result = await getLibraryLink(hre, libraries, 'path/to/contract.zbin'); + + expect(result).to.deep.equal({ + contractZbinPath: 'path/to/contract.zbin', + libraries: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'contracts/Lib1.sol:Lib1': '0x1234567890123456789012345678901234567890', + // eslint-disable-next-line @typescript-eslint/naming-convention + 'contracts/Lib2.sol:Lib2': '0x0987654321098765432109876543210987654321', + }, + }); + }); + + it('should handle errors when reading artifacts', async () => { + hre.artifacts.readArtifact.withArgs('Lib1').rejects(new Error('Artifact not found')); + + const libraries = { + Lib1: '0x1234567890123456789012345678901234567890', + }; + + try { + await getLibraryLink(hre, libraries, 'path/to/contract.zbin'); + } catch (error: any) { + expect(error.message).to.equal('Artifact not found'); + } + }); +}); From 00e4b871dfdc48ac82e7b01b85502978a4d3feef Mon Sep 17 00:00:00 2001 From: Marko Arambasic Date: Thu, 24 Oct 2024 17:35:53 +0200 Subject: [PATCH 5/5] fix: change compiler version and adjust libraries for compiler command line --- packages/hardhat-zksync-solc/src/compile/binary.ts | 2 +- packages/hardhat-zksync-solc/src/compile/index.ts | 3 ++- packages/hardhat-zksync-solc/src/constants.ts | 2 +- packages/hardhat-zksync-solc/src/index.ts | 1 + packages/hardhat-zksync-solc/src/plugin.ts | 5 +++++ 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/hardhat-zksync-solc/src/compile/binary.ts b/packages/hardhat-zksync-solc/src/compile/binary.ts index 309ab8792..0d040cbae 100644 --- a/packages/hardhat-zksync-solc/src/compile/binary.ts +++ b/packages/hardhat-zksync-solc/src/compile/binary.ts @@ -53,7 +53,7 @@ export async function linkWithBinary(config: ZkSolcConfig, linkLibraries: LinkLi if (linkLibraries.libraries) { processCommand += ` --libraries ${Object.entries(linkLibraries.libraries) .map((lib) => `${lib[0]}=${lib[1]}`) - .join(',')}`; + .join(' ')}`; } const output: string = await new Promise((resolve, reject) => { const process = exec( diff --git a/packages/hardhat-zksync-solc/src/compile/index.ts b/packages/hardhat-zksync-solc/src/compile/index.ts index 9741e724e..682bb4680 100644 --- a/packages/hardhat-zksync-solc/src/compile/index.ts +++ b/packages/hardhat-zksync-solc/src/compile/index.ts @@ -7,6 +7,7 @@ import { findMissingLibraries, mapMissingLibraryDependencies, writeLibrariesToFi import { DETECT_MISSING_LIBRARY_MODE_COMPILER_VERSION, ZKSOLC_COMPILER_MIN_VERSION_WITH_FALLBACK_OZ, + ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING, } from '../constants'; import { validateDockerIsInstalled, @@ -66,7 +67,7 @@ export class BinaryCompiler implements ICompiler { // Check for missing libraries if ( semver.gte(config.version, DETECT_MISSING_LIBRARY_MODE_COMPILER_VERSION) && - semver.lt(config.version, '1.6.0') + semver.lt(config.version, ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING) ) { const zkSolcOutput = await compileWithBinary(input, config, this.solcPath, true); diff --git a/packages/hardhat-zksync-solc/src/constants.ts b/packages/hardhat-zksync-solc/src/constants.ts index 035e7f8e6..e22cb596e 100644 --- a/packages/hardhat-zksync-solc/src/constants.ts +++ b/packages/hardhat-zksync-solc/src/constants.ts @@ -15,7 +15,7 @@ export const TASK_DOWNLOAD_ZKSOLC = 'compile:zksolc:download'; export const ZKSOLC_COMPILER_PATH_VERSION = 'local_or_remote'; export const TASK_COMPILE_LINK: string = 'compile:link'; -export const ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING = '1.6.0'; +export const ZKSOLC_COMPILER_VERSION_WITH_LIBRARY_LINKING = '1.5.4'; export const defaultZkSolcConfig: ZkSolcConfig = { version: 'latest', diff --git a/packages/hardhat-zksync-solc/src/index.ts b/packages/hardhat-zksync-solc/src/index.ts index 6dc8eb4dd..451197558 100644 --- a/packages/hardhat-zksync-solc/src/index.ts +++ b/packages/hardhat-zksync-solc/src/index.ts @@ -139,6 +139,7 @@ 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) => { diff --git a/packages/hardhat-zksync-solc/src/plugin.ts b/packages/hardhat-zksync-solc/src/plugin.ts index 12c923d77..f37536faf 100644 --- a/packages/hardhat-zksync-solc/src/plugin.ts +++ b/packages/hardhat-zksync-solc/src/plugin.ts @@ -17,6 +17,7 @@ export async function compileLink( sourceName: string; contractName: string; libraries?: { [libraryName: string]: string }; + withoutError?: boolean; }, hre: HardhatRuntimeEnvironment, ): Promise { @@ -43,6 +44,10 @@ export async function compileLink( const output = await link(hre.config.zksolc, await getLibraryLink(hre, taskArgs.libraries, contractFilePath)); if (!lodash.isEmpty(output.unlinked)) { + if (taskArgs.withoutError) { + return undefined; + } + throw new ZkSyncSolcPluginError( `Libraries for contract ${contractFQN} are not linked: ${Object.values(output.unlinked[contractFilePath]) .map((lib) => `${lib}`)