Skip to content

Commit

Permalink
feat: zksync support for core solidity package (#5205)
Browse files Browse the repository at this point in the history
### Description

ZKSync PR 1

This PR introduces ZKSync support for Solidity contracts and
restructures build files directory structure.

### Drive-by changes

- Added ZKSync compilation support using zksolc via
`@matterlabs/hardhat-zksync-solc`
- Restructured typechain directory location to `core-utils/typechain`
- Decoupled Hardhat configuration for ZKSync from EVM.
- Updated build process to handle both standard and ZKSync contract
artifacts

### Related issues

None

### Backward compatibility

Yes

### Testing

Testing was previously in [feat: zksync
support](#4725)
PR

---------

Co-authored-by: Morteza Shojaei <[email protected]>
Co-authored-by: mshojaei-txfusion <[email protected]>
Co-authored-by: Le Yu <[email protected]>
  • Loading branch information
4 people authored Jan 22, 2025
1 parent a352bc8 commit db8c090
Show file tree
Hide file tree
Showing 16 changed files with 705 additions and 25 deletions.
11 changes: 11 additions & 0 deletions .changeset/chatty-starfishes-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@hyperlane-xyz/core': minor
---

Add ZKSync support and restructure build artifacts:

- Add ZKSync compilation support
- Restructure typechain directory location to core-utils/typechain
- Add ZKSync-specific artifact generation and exports
- Update build process to handle both standard and ZKSync artifacts
- Add new exports for ZKSync build artifacts and contract types
7 changes: 7 additions & 0 deletions solidity/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ docs
flattened/
buildArtifact.json
fixtures/
# ZKSync
artifacts-zk
cache-zk
core-utils/zksync/artifacts/output
.zksolc-libraries-cache/
typechain-types/
typechain/
2 changes: 2 additions & 0 deletions solidity/core-utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './typechain/index.js';
export * from './zksync/index.js';
103 changes: 103 additions & 0 deletions solidity/core-utils/zksync/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { promises as fsPromises } from 'fs';
import path, { join } from 'path';
import { fileURLToPath } from 'url';

/**
* @dev Represents a ZkSync artifact.
*/
export type ZKSyncArtifact = {
contractName: string;
sourceName: string;
abi: any;
bytecode: string;
deployedBytecode: string;
factoryDeps?: Record<string, string>;
};

/**
* @dev A mapping of artifact names to their corresponding ZkSync artifacts.
*/
export type ArtifactMap = {
[key: string]: ZKSyncArtifact;
};

// Get the resolved path to the current file
const currentFilePath = fileURLToPath(import.meta.url);
const currentDirectory = path.dirname(currentFilePath);

/**
* @dev Reads artifact files from the specified directory.
* @param directory The directory to read artifact files from.
* @return An array of artifact file names that end with '.json'.
*/
async function getArtifactFiles(directory: string): Promise<string[]> {
return fsPromises
.readdir(directory)
.then((files) => files.filter((file) => file.endsWith('.json')));
}

/**
* @dev Exports the list of artifact names without the .json extension.
* @return An array of artifact names without the .json extension.
*/
export async function getZKSyncArtifactNames(): Promise<string[]> {
return getArtifactFiles(join(currentDirectory, 'artifacts')).then((files) =>
files.map((file) => file.replace('.json', '')),
);
}

/**
* @dev Checks if a ZkSync artifact exists by its name.
* @param name The name of the artifact to check.
* @return True if the artifact exists, false otherwise.
*/
export async function artifactExists(name: string): Promise<boolean> {
const artifactNames = await getZKSyncArtifactNames();
return artifactNames.includes(name);
}

/**
* @dev Loads a ZkSync artifact by its name.
* @param name The name of the artifact to load.
* @return The loaded ZKSyncArtifact or undefined if it cannot be loaded.
*/
export async function loadZKSyncArtifact(
name: string,
): Promise<ZKSyncArtifact | undefined> {
try {
const artifactPath = join(currentDirectory, 'artifacts', `${name}.json`);
const artifactContent = await fsPromises.readFile(artifactPath, 'utf-8');
return JSON.parse(artifactContent);
} catch (error) {
console.error(`Error loading artifact: ${name}`, error);
return undefined;
}
}

/**
* @dev Loads all ZkSync artifacts into a map.
* @return A map of artifact names to their corresponding ZkSync artifacts.
*/
export async function loadAllZKSyncArtifacts(): Promise<ArtifactMap> {
const zkSyncArtifactMap: ArtifactMap = {};
const zksyncArtifactNames = await getZKSyncArtifactNames();
for (const artifactName of zksyncArtifactNames) {
const artifact = await loadZKSyncArtifact(artifactName);
if (artifact) {
zkSyncArtifactMap[artifactName] = artifact;
}
}

return zkSyncArtifactMap;
}

/**
* @dev Retrieves a specific ZkSync artifact by its file name.
* @param name The name of the artifact to retrieve.
* @return The loaded ZkSyncArtifact or undefined if it cannot be loaded.
*/
export async function getZKSyncArtifactByName(
name: string,
): Promise<ZKSyncArtifact | undefined> {
return loadZKSyncArtifact(name);
}
39 changes: 39 additions & 0 deletions solidity/exportBuildArtifact.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,42 @@ else
echo 'Failed to process build artifact with jq'
exit 1
fi

# ZKSYNC

if [ "$ZKSYNC" = "true" ]; then
# Define the artifacts directory
artifactsDir="./artifacts-zk/build-info"
# Define the output file
outputFileJson="./dist/zksync/buildArtifact.json"
outputFileJs="./dist/zksync/buildArtifact.js"
outputFileTsd="./dist/zksync/buildArtifact.d.ts"

# log that we're in the script
echo 'Finding and processing ZKSync hardhat build artifact...'

# Find most recently modified JSON build artifact
if [ "$(uname)" = "Darwin" ]; then
# for local flow
jsonFiles=$(find "$artifactsDir" -type f -name "*.json" -exec stat -f "%m %N" {} \; | sort -rn | head -n 1 | cut -d' ' -f2-)
else
# for CI flow
jsonFiles=$(find "$artifactsDir" -type f -name "*.json" -exec stat -c "%Y %n" {} \; | sort -rn | head -n 1 | cut -d' ' -f2-)
fi

if [ ! -f "$jsonFiles" ]; then
echo 'Failed to find ZKSync build artifact'
exit 1
fi

# Extract required keys and write to outputFile
if jq -c '{input, solcLongVersion, zk_version: .output.zk_version}' "$jsonFiles" >"$outputFileJson"; then
echo "export const buildArtifact = " >"$outputFileJs"
cat "$outputFileJson" >>"$outputFileJs"
echo "export const buildArtifact: any" >"$outputFileTsd"
echo 'Finished processing ZKSync build artifact.'
else
echo 'Failed to process ZKSync build artifact with jq'
exit 1
fi
fi
60 changes: 60 additions & 0 deletions solidity/generate-artifact-exports.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { promises as fsPromises } from 'fs';
import { basename, dirname, join } from 'path';
import { glob } from 'typechain';
import { fileURLToPath } from 'url';

const cwd = process.cwd();

/**
* @dev Only includes primary JSON artifacts & excludes debug files and build-info directory
*/
const zksyncArtifacts = glob(cwd, [
`!./artifacts-zk/!(build-info)/**/*.dbg.json`,
`./artifacts-zk/!(build-info)/**/+([a-zA-Z0-9_]).json`,
]);

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const srcOutputDir = join(__dirname, 'core-utils/zksync/artifacts');

// Ensure output directory exists
await fsPromises.mkdir(srcOutputDir, { recursive: true });

/**
* @dev Processes a single artifact file
*/
async function processArtifactFile(file) {
const fileName = `${basename(file, '.json')}`;
const outputFile = join(srcOutputDir, `${fileName}.json`);

// Check if file already exists
const fileExists = await fsPromises
.access(outputFile)
.then(() => true)
.catch(() => false);

if (fileExists) {
// File already exists, skipping...
// NOTE: Hardhat compiler produces duplicate artifacts when
// shared interfaces/libraries are used across different contracts
// This is expected behavior and we only need one copy of each artifact
return;
}

const fileContent = await fsPromises.readFile(file, { encoding: 'utf-8' });
await fsPromises.writeFile(outputFile, fileContent);
}

/**
* @dev Reads each artifact file and writes it to srcOutputDir concurrently
*/
await Promise.all(
zksyncArtifacts.map(async (file) => {
try {
await processArtifactFile(file);
} catch (error) {
console.error(`Error processing file ${file}:`, error);
}
}),
);
2 changes: 1 addition & 1 deletion solidity/hardhat.config.cts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = {
currency: 'USD',
},
typechain: {
outDir: './types',
outDir: './core-utils/typechain',
target: 'ethers-v5',
alwaysGenerateOverloads: true,
node16Modules: true,
Expand Down
15 changes: 11 additions & 4 deletions solidity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"devDependencies": {
"@layerzerolabs/solidity-examples": "^1.1.0",
"@matterlabs/hardhat-zksync-solc": "^1.2.4",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@nomiclabs/hardhat-waffle": "^2.0.6",
"@typechain/ethers-v5": "^11.1.2",
Expand All @@ -35,17 +36,19 @@
"ts-node": "^10.8.0",
"tsx": "^4.19.1",
"typechain": "patch:typechain@npm%3A8.3.2#~/.yarn/patches/typechain-npm-8.3.2-b02e27439e.patch",
"typescript": "5.3.3"
"typescript": "5.3.3",
"zksync-ethers": "^5.10.0"
},
"directories": {
"test": "test"
},
"type": "module",
"exports": {
".": "./dist/index.js",
"./mailbox": "./dist/contracts/Mailbox.js",
"./mailbox": "./dist/typechain/contracts/Mailbox.js",
"./buildArtifact.js": "./dist/buildArtifact.js",
"./buildArtifact.json": "./dist/buildArtifact.json",
"./buildArtifact-zksync.js": "./dist/zksync/buildArtifact.js",
"./contracts": "./contracts"
},
"types": "./dist/index.d.ts",
Expand All @@ -65,12 +68,15 @@
"license": "Apache-2.0",
"scripts": {
"build": "yarn version:update && yarn hardhat-esm compile && tsc && ./exportBuildArtifact.sh",
"build:zk": "yarn hardhat-zk compile && ts-node generate-artifact-exports.mjs && ZKSYNC=true ./exportBuildArtifact.sh && yarn copy-artifacts",
"prepublishOnly": "yarn build && yarn build:zk",
"lint": "solhint contracts/**/*.sol",
"clean": "yarn hardhat-esm clean && rm -rf ./dist ./cache ./types ./coverage ./out ./forge-cache ./fixtures",
"clean": "yarn hardhat-esm clean && yarn hardhat-zk clean && rm -rf ./dist ./cache ./cache-zk ./types ./coverage ./out ./forge-cache ./fixtures",
"coverage": "yarn fixtures && ./coverage.sh",
"docs": "forge doc",
"fixtures": "mkdir -p ./fixtures/aggregation ./fixtures/multisig",
"hardhat-esm": "NODE_OPTIONS='--experimental-loader ts-node/esm/transpile-only --no-warnings=ExperimentalWarning' hardhat --config hardhat.config.cts",
"hardhat-zk": "NODE_OPTIONS='--experimental-loader ts-node/esm/transpile-only --no-warnings=ExperimentalWarning' hardhat --config zk-hardhat.config.cts",
"prettier": "prettier --write ./contracts ./test",
"test": "yarn version:exhaustive && yarn hardhat-esm test && yarn test:forge",
"test:hardhat": "yarn hardhat-esm test",
Expand All @@ -82,7 +88,8 @@
"storage": "./storage.sh",
"version:update": "sh ./bytecodeversion.sh",
"version:changed": "yarn version:update && git diff --exit-code",
"version:exhaustive": "yarn tsx ./test/exhaustiveversion.test.ts"
"version:exhaustive": "yarn tsx ./test/exhaustiveversion.test.ts",
"copy-artifacts": "mkdir -p dist/zksync/artifacts && cp core-utils/zksync/artifacts/*.json dist/zksync/artifacts/"
},
"peerDependencies": {
"@ethersproject/abi": "*",
Expand Down
4 changes: 2 additions & 2 deletions solidity/test/lib/mailboxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import {
LegacyMultisigIsm,
TestMailbox,
TestMerkleTreeHook,
} from '../../types';
import { DispatchEvent } from '../../types/contracts/Mailbox';
} from '../../core-utils/typechain';
import { DispatchEvent } from '../../core-utils/typechain/contracts/Mailbox';

export type MessageAndProof = {
proof: MerkleProof;
Expand Down
2 changes: 1 addition & 1 deletion solidity/test/merkle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai';
import { utils } from 'ethers';

import merkleTestCases from '../../vectors/merkle.json' assert { type: 'json' };
import { TestMerkle, TestMerkle__factory } from '../types';
import { TestMerkle, TestMerkle__factory } from '../core-utils/typechain';

import { getSigner } from './signer';

Expand Down
6 changes: 5 additions & 1 deletion solidity/test/message.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
} from '@hyperlane-xyz/utils';

import testCases from '../../vectors/message.json' assert { type: 'json' };
import { Mailbox__factory, TestMessage, TestMessage__factory } from '../types';
import {
Mailbox__factory,
TestMessage,
TestMessage__factory,
} from '../core-utils/typechain';

import { getSigner, getSigners } from './signer';

Expand Down
5 changes: 4 additions & 1 deletion solidity/test/mockMailbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { utils } from 'ethers';

import { addressToBytes32 } from '@hyperlane-xyz/utils';

import { MockMailbox__factory, TestRecipient__factory } from '../types';
import {
MockMailbox__factory,
TestRecipient__factory,
} from '../core-utils/typechain';

import { getSigner } from './signer';

Expand Down
2 changes: 1 addition & 1 deletion solidity/test/testrecipient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { utils } from 'ethers';

import { addressToBytes32 } from '@hyperlane-xyz/utils';

import { TestRecipient, TestRecipient__factory } from '../types';
import { TestRecipient, TestRecipient__factory } from '../core-utils/typechain';

import { getSigner } from './signer';

Expand Down
4 changes: 2 additions & 2 deletions solidity/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./types"
"rootDir": "./core-utils"
},
"exclude": ["./test", "hardhat.config.cts", "./dist"]
"exclude": ["./test", "hardhat.config.cts", "./dist", "zk-hardhat.config.cts"]
}
Loading

0 comments on commit db8c090

Please sign in to comment.