diff --git a/.changeset/chatty-starfishes-double.md b/.changeset/chatty-starfishes-double.md new file mode 100644 index 0000000000..2e51bb44da --- /dev/null +++ b/.changeset/chatty-starfishes-double.md @@ -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 diff --git a/.changeset/eleven-cows-dance.md b/.changeset/eleven-cows-dance.md new file mode 100644 index 0000000000..adc16422bd --- /dev/null +++ b/.changeset/eleven-cows-dance.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Added ZKSync specific deployment logic and artifact related utils diff --git a/.changeset/fresh-mice-matter.md b/.changeset/fresh-mice-matter.md new file mode 100644 index 0000000000..5bfd094fff --- /dev/null +++ b/.changeset/fresh-mice-matter.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Support for zksync on deployments and verifications diff --git a/.changeset/honest-readers-lick.md b/.changeset/honest-readers-lick.md new file mode 100644 index 0000000000..3c6aaf2e3d --- /dev/null +++ b/.changeset/honest-readers-lick.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Check for ZKSync contracts and functionalities support diff --git a/.changeset/mighty-terms-rest.md b/.changeset/mighty-terms-rest.md new file mode 100644 index 0000000000..4f0598feb1 --- /dev/null +++ b/.changeset/mighty-terms-rest.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +ZKSync Provider types with builders diff --git a/.changeset/orange-cats-stare.md b/.changeset/orange-cats-stare.md new file mode 100644 index 0000000000..a1c431b5e4 --- /dev/null +++ b/.changeset/orange-cats-stare.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Add ZKSync contract verification with custom compiler options and refactor verification classes diff --git a/solidity/.gitignore b/solidity/.gitignore index 6f01b8e770..d7ca1005d0 100644 --- a/solidity/.gitignore +++ b/solidity/.gitignore @@ -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/ \ No newline at end of file diff --git a/solidity/core-utils/index.ts b/solidity/core-utils/index.ts new file mode 100644 index 0000000000..1fbeb99f89 --- /dev/null +++ b/solidity/core-utils/index.ts @@ -0,0 +1,2 @@ +export * from './typechain/index.js'; +export * from './zksync/index.js'; diff --git a/solidity/core-utils/zksync/artifacts.ts b/solidity/core-utils/zksync/artifacts.ts new file mode 100644 index 0000000000..86c6b2dcf5 --- /dev/null +++ b/solidity/core-utils/zksync/artifacts.ts @@ -0,0 +1,5 @@ +import type { ZKSyncArtifact } from './types.js'; + +// Default empty artifact array when `yarn build:zk` hasn't been run +// This file will be populated with contract artifacts in dist after running the build:zk command +export const zkSyncContractArtifacts: ZKSyncArtifact[] = [] as const; diff --git a/solidity/core-utils/zksync/buildArtifact.ts b/solidity/core-utils/zksync/buildArtifact.ts new file mode 100644 index 0000000000..6405cdef48 --- /dev/null +++ b/solidity/core-utils/zksync/buildArtifact.ts @@ -0,0 +1,19 @@ +// Default empty artifact array when `yarn build:zk` hasn't been run +// This file will be populated with build artifacts in dist/zksync after running the build:zk command +export const buildArtifact = { + solcLongVersion: '', + zk_version: '', + input: { + language: 'Solidity', + sources: {}, + settings: { + optimizer: { + enabled: false, + runs: 200, + }, + outputSelection: {}, + evmVersion: 'london', + remappings: [], + }, + }, +}; diff --git a/solidity/core-utils/zksync/index.ts b/solidity/core-utils/zksync/index.ts new file mode 100644 index 0000000000..f27a955d7c --- /dev/null +++ b/solidity/core-utils/zksync/index.ts @@ -0,0 +1,4 @@ +export * from './artifacts.js'; +export * from './buildArtifact.js'; +export * from './utils.js'; +export * from './types.js'; diff --git a/solidity/core-utils/zksync/types.ts b/solidity/core-utils/zksync/types.ts new file mode 100644 index 0000000000..eba36c83ce --- /dev/null +++ b/solidity/core-utils/zksync/types.ts @@ -0,0 +1,11 @@ +export interface ZKSyncArtifact { + _format: string; + contractName: string; + sourceName: string; + abi: any[]; + bytecode: string; + deployedBytecode: string; + linkReferences: Record; + deployedLinkReferences: Record; + factoryDeps: Record; +} diff --git a/solidity/core-utils/zksync/utils.ts b/solidity/core-utils/zksync/utils.ts new file mode 100644 index 0000000000..64be6859a1 --- /dev/null +++ b/solidity/core-utils/zksync/utils.ts @@ -0,0 +1,23 @@ +import { zkSyncContractArtifacts } from './artifacts.js'; +import { ZKSyncArtifact } from './types.js'; + +/** + * @dev Get a ZkSync artifact by its name. + * @param name The name of the artifact to get. + * @return The loaded ZKSyncArtifact or undefined if it cannot be found. + */ +export function getZKSyncArtifactByName( + name: string, +): ZKSyncArtifact | undefined { + return zkSyncContractArtifacts.find( + (artifact) => artifact.contractName === name, + ); +} + +/** + * @dev Loads all ZkSync artifacts into an array. + * @return An array of ZkSync artifacts. + */ +export function loadAllZKSyncArtifacts(): ZKSyncArtifact[] { + return zkSyncContractArtifacts; +} diff --git a/solidity/exportBuildArtifact.sh b/solidity/exportBuildArtifact.sh index e6d5d503b8..c951281911 100755 --- a/solidity/exportBuildArtifact.sh +++ b/solidity/exportBuildArtifact.sh @@ -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 \ No newline at end of file diff --git a/solidity/generate-artifact-exports.mjs b/solidity/generate-artifact-exports.mjs new file mode 100755 index 0000000000..69327ffda4 --- /dev/null +++ b/solidity/generate-artifact-exports.mjs @@ -0,0 +1,174 @@ +import { promises as fs } from 'fs'; +import { basename, dirname, join } from 'path'; +import { glob } from 'typechain'; +import { fileURLToPath } from 'url'; + +const cwd = process.cwd(); +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const ROOT_OUTPUT_DIR = join(__dirname, 'dist/zksync/'); +const ARTIFACTS_OUTPUT_DIR = join(ROOT_OUTPUT_DIR, 'artifacts'); + +/** + * @notice Templates for TypeScript artifact generation + */ +const TEMPLATES = { + JS_ARTIFACT: `\ +export const {name} = {artifact}; +`, + + DTS_ARTIFACT: `\ +import type { ZKSyncArtifact } from '../types.js'; + +export declare const {name}: ZKSyncArtifact; +`, + + JS_INDEX: `\ +{imports} + +export const zkSyncContractArtifacts = [ +{exports} +]; +`, + + DTS_INDEX: `\ +import type { ZKSyncArtifact } from './types.js'; + +export declare const zkSyncContractArtifacts: readonly ZKSyncArtifact[]; +`, +}; + +class ArtifactGenerator { + constructor() { + this.processedFiles = new Set(); + } + + /** + * @notice Retrieves paths of all relevant artifact files + * @dev Excludes debug files and build-info directory + * @return {string[]} Array of file paths matching the glob pattern + */ + getArtifactPaths() { + return glob(cwd, [ + `!./artifacts-zk/!(build-info)/**/*.dbg.json`, + `./artifacts-zk/!(build-info)/**/+([a-zA-Z0-9_]).json`, + ]); + } + + /** + * @notice Creates the output directory if it doesn't exist + */ + async createOutputDirectory() { + await fs.mkdir(ARTIFACTS_OUTPUT_DIR, { recursive: true }); + } + + /** + * @notice Reads and parses a JSON artifact file + * @param filePath Path to the artifact file + * @return {Promise} Parsed JSON content + */ + async readArtifactFile(filePath) { + const content = await fs.readFile(filePath, 'utf-8'); + return JSON.parse(content); + } + + /** + * @notice Generates JavaScript content for a contract artifact + */ + generateJavaScriptContent(name, artifact) { + return TEMPLATES.JS_ARTIFACT.replace('{name}', name).replace( + '{artifact}', + JSON.stringify(artifact, null, 2), + ); + } + + /** + * @notice Generates TypeScript declaration content for a contract artifact + */ + generateDeclarationContent(name) { + return TEMPLATES.DTS_ARTIFACT.replace('{name}', name); + } + + /** + * @notice Generates index file contents + */ + generateIndexContents(artifactNames) { + const imports = artifactNames + .map((name) => `import { ${name} } from './artifacts/${name}.js';`) + .join('\n'); + const exports = artifactNames.map((name) => ` ${name},`).join('\n'); + + const jsContent = TEMPLATES.JS_INDEX.replace('{imports}', imports).replace( + '{exports}', + exports, + ); + + const dtsContent = TEMPLATES.DTS_INDEX.replace( + '{imports}', + imports, + ).replace('{exports}', exports); + + return { jsContent, dtsContent }; + } + + /** + * @notice Processes a single artifact file + */ + async processArtifact(filePath) { + const name = basename(filePath, '.json'); + + if (this.processedFiles.has(name)) { + return; + } + + const artifact = await this.readArtifactFile(filePath); + + // Generate and write .js file + const jsContent = this.generateJavaScriptContent(name, artifact); + await fs.writeFile( + join(ROOT_OUTPUT_DIR, 'artifacts', `${name}.js`), + jsContent, + ); + + // Generate and write .d.ts file + const dtsContent = this.generateDeclarationContent(name); + await fs.writeFile( + join(ROOT_OUTPUT_DIR, 'artifacts', `${name}.d.ts`), + dtsContent, + ); + + this.processedFiles.add(name); + } + + async generate() { + try { + await this.createOutputDirectory(); + + const artifactPaths = this.getArtifactPaths(); + + for (const filePath of artifactPaths) { + await this.processArtifact(filePath); + } + + const processedNames = Array.from(this.processedFiles); + + // Generate and write index files + const { jsContent, dtsContent } = + this.generateIndexContents(processedNames); + + await fs.writeFile(join(ROOT_OUTPUT_DIR, 'artifacts.js'), jsContent); + await fs.writeFile(join(ROOT_OUTPUT_DIR, 'artifacts.d.ts'), dtsContent); + + console.log( + `Successfully processed ${processedNames.length} zksync artifacts`, + ); + } catch (error) { + console.error('Error processing zksync artifacts:', error); + throw error; + } + } +} + +const generator = new ArtifactGenerator(); +generator.generate().catch(console.error); diff --git a/solidity/hardhat.config.cts b/solidity/hardhat.config.cts index 6d64d971e6..6fd31c3dc4 100644 --- a/solidity/hardhat.config.cts +++ b/solidity/hardhat.config.cts @@ -22,7 +22,7 @@ module.exports = { currency: 'USD', }, typechain: { - outDir: './types', + outDir: './core-utils/typechain', target: 'ethers-v5', alwaysGenerateOverloads: true, node16Modules: true, diff --git a/solidity/package.json b/solidity/package.json index 755cf5fbbb..cd87f72e6d 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -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", @@ -35,7 +36,8 @@ "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" @@ -43,9 +45,10 @@ "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", @@ -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 && tsc && ts-node generate-artifact-exports.mjs && ZKSYNC=true ./exportBuildArtifact.sh", + "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", diff --git a/solidity/test/lib/mailboxes.ts b/solidity/test/lib/mailboxes.ts index 06ae211cf5..bd1d728e36 100644 --- a/solidity/test/lib/mailboxes.ts +++ b/solidity/test/lib/mailboxes.ts @@ -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; diff --git a/solidity/test/merkle.test.ts b/solidity/test/merkle.test.ts index f2a3d5d399..5546ac86bf 100644 --- a/solidity/test/merkle.test.ts +++ b/solidity/test/merkle.test.ts @@ -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'; diff --git a/solidity/test/message.test.ts b/solidity/test/message.test.ts index 83bde28b6f..996bb31e7b 100644 --- a/solidity/test/message.test.ts +++ b/solidity/test/message.test.ts @@ -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'; diff --git a/solidity/test/mockMailbox.test.ts b/solidity/test/mockMailbox.test.ts index db49a7585e..6df9ec41db 100644 --- a/solidity/test/mockMailbox.test.ts +++ b/solidity/test/mockMailbox.test.ts @@ -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'; diff --git a/solidity/test/testrecipient.test.ts b/solidity/test/testrecipient.test.ts index acdbf573eb..dd1d9a44e1 100644 --- a/solidity/test/testrecipient.test.ts +++ b/solidity/test/testrecipient.test.ts @@ -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'; diff --git a/solidity/tsconfig.json b/solidity/tsconfig.json index cece6d7fc6..99ae657d2b 100644 --- a/solidity/tsconfig.json +++ b/solidity/tsconfig.json @@ -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"] } diff --git a/solidity/zk-hardhat.config.cts b/solidity/zk-hardhat.config.cts new file mode 100644 index 0000000000..618fe65a5b --- /dev/null +++ b/solidity/zk-hardhat.config.cts @@ -0,0 +1,46 @@ +import '@matterlabs/hardhat-zksync-solc'; +import '@nomiclabs/hardhat-ethers'; +import 'hardhat-ignore-warnings'; + +/** + * @type import('hardhat/config').HardhatUserConfig + */ +module.exports = { + zksolc: { + version: '1.5.3', + compilerSource: 'binary', + enableEraVMExtensions: true, + }, + defaultNetwork: 'zkSyncNetwork', + networks: { + zkSyncNetwork: { + url: 'http://127.0.0.1:8011', + ethNetwork: '', + zksync: true, + }, + }, + solidity: { + version: '0.8.19', + settings: { + optimizer: { + enabled: true, + runs: 999_999, + }, + }, + }, + mocha: { + bail: true, + import: 'tsx', + }, + warnings: { + // turn off all warnings for libs: + 'fx-portal/**/*': { + default: 'off', + }, + }, + paths: { + sources: './contracts', + cache: './cache-zk', + artifacts: './artifacts-zk', + }, +}; diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 79f67348c7..2ed4ea9790 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -21,6 +21,7 @@ "ethers": "^5.7.2", "pino": "^8.19.0", "viem": "^2.21.45", + "zksync-ethers": "^5.10.0", "zod": "^3.21.2" }, "devDependencies": { diff --git a/typescript/sdk/src/core/EvmCoreModule.ts b/typescript/sdk/src/core/EvmCoreModule.ts index 60dad3f523..ff387ccc1f 100644 --- a/typescript/sdk/src/core/EvmCoreModule.ts +++ b/typescript/sdk/src/core/EvmCoreModule.ts @@ -35,12 +35,16 @@ import { proxyFactoryFactories, } from '../deploy/contracts.js'; import { proxyAdminUpdateTxs } from '../deploy/proxy.js'; +import { createDefaultProxyFactoryFactories } from '../deploy/proxyFactoryUtils.js'; +import { ProxyFactoryFactoriesAddresses } from '../deploy/types.js'; import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { HookFactories } from '../hook/contracts.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { DerivedIsmConfig } from '../ism/EvmIsmReader.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { IsmConfig } from '../ism/types.js'; +import { isStaticDeploymentSupported } from '../ism/utils.js'; +import { ChainTechnicalStack } from '../metadata/chainMetadataTypes.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { ChainName, ChainNameOrId } from '../types.js'; @@ -294,14 +298,16 @@ export class EvmCoreModule extends HyperlaneModule< contractVerifier?: ContractVerifier; }): Promise { const { config, multiProvider, chain, contractVerifier } = params; - const chainName = multiProvider.getChainName(chain); + const { name: chainName, technicalStack } = + multiProvider.getChainMetadata(chain); - const ismFactoryFactories = await EvmCoreModule.deployIsmFactories({ - chainName, - config, - multiProvider, - contractVerifier, - }); + const ismFactoryFactories: ProxyFactoryFactoriesAddresses = + await this.getIsmFactoryFactories(technicalStack, { + chainName, + config, + multiProvider, + contractVerifier, + }); const ismFactory = new HyperlaneIsmFactory( attachContractsMap( @@ -396,7 +402,6 @@ export class EvmCoreModule extends HyperlaneModule< // Set Core & extra addresses return { ...ismFactoryFactories, - proxyAdmin: proxyAdmin.address, mailbox: mailbox.address, interchainAccountRouter, @@ -501,4 +506,31 @@ export class EvmCoreModule extends HyperlaneModule< ); return mailbox; } + + /** + * Retrieves the ISM factory factories based on the provided protocol and parameters. + * + * @param protocol - The protocol type to determine if static address set deployment should be skipped. + * @param params - An object containing the parameters needed for ISM factory deployment. + * @param params.chainName - The name of the chain for which the ISM factories are being deployed. + * @param params.config - The core configuration to be used during deployment. + * @param params.multiProvider - The multi-provider instance for interacting with the blockchain. + * @param params.contractVerifier - An optional contract verifier for validating contracts during deployment. + * @returns A promise that resolves to the addresses of the deployed ISM factory factories. + */ + private static async getIsmFactoryFactories( + technicalStack: ChainTechnicalStack | undefined, + params: { + chainName: string; + config: CoreConfig; + multiProvider: MultiProvider; + contractVerifier?: ContractVerifier; + }, + ): Promise { + // Check if we should skip static address set deployment + if (!isStaticDeploymentSupported(technicalStack)) { + return createDefaultProxyFactoryFactories(); + } + return EvmCoreModule.deployIsmFactories(params); + } } diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index 10d4415ee7..049f67477b 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -33,6 +33,7 @@ import { } from '../contracts/types.js'; import { DerivedHookConfig, EvmHookReader } from '../hook/EvmHookReader.js'; import { DerivedIsmConfig, EvmIsmReader } from '../ism/EvmIsmReader.js'; +import { ChainTechnicalStack } from '../metadata/chainMetadataTypes.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { RouterConfig } from '../router/types.js'; import { ChainMap, ChainName, OwnableConfig } from '../types.js'; @@ -252,6 +253,13 @@ export class HyperlaneCore extends HyperlaneApp { } async estimateHandle(message: DispatchedMessage): Promise { + // This estimation is not possible on zksync as it is overriding transaction.from + // transaction.from must be a signer on zksync + if ( + this.multiProvider.getChainMetadata(this.getDestination(message)) + .technicalStack === ChainTechnicalStack.ZkSync + ) + return '0'; return ( await this.getRecipient(message).estimateGas.handle( message.parsed.origin, diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index fb3262902b..92d9f45a7e 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -32,10 +32,15 @@ import { HookConfig } from '../hook/types.js'; import type { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { IsmConfig } from '../ism/types.js'; import { moduleMatchesConfig } from '../ism/utils.js'; +import { + ChainTechnicalStack, + ExplorerFamily, +} from '../metadata/chainMetadataTypes.js'; import { InterchainAccount } from '../middleware/account/InterchainAccount.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { MailboxClientConfig } from '../router/types.js'; import { ChainMap, ChainName, OwnableConfig } from '../types.js'; +import { getZKSyncArtifactByContractName } from '../utils/zksync.js'; import { UpgradeConfig, @@ -46,6 +51,7 @@ import { proxyImplementation, } from './proxy.js'; import { ContractVerifier } from './verify/ContractVerifier.js'; +import { ZKSyncContractVerifier } from './verify/ZKSyncContractVerifier.js'; import { ContractVerificationInput, ExplorerLicenseType, @@ -53,6 +59,7 @@ import { import { buildVerificationInput, getContractVerificationInput, + getContractVerificationInputForZKSync, shouldAddVerificationInput, } from './verify/utils.js'; @@ -115,6 +122,15 @@ export abstract class HyperlaneDeployer< return this.options.contractVerifier?.verifyContract(chain, input, logger); } + async verifyContractForZKSync( + chain: ChainName, + input: ContractVerificationInput, + logger = this.logger, + ): Promise { + const verifier = new ZKSyncContractVerifier(this.multiProvider); + return verifier.verifyContract(chain, input, logger); + } + abstract deployContracts( chain: ChainName, config: Config, @@ -396,10 +412,19 @@ export abstract class HyperlaneDeployer< ', ', )})...`, ); + + const explorer = this.multiProvider.tryGetExplorerApi(chain); + const { technicalStack } = this.multiProvider.getChainMetadata(chain); + const isZKSyncExplorer = explorer?.family === ExplorerFamily.ZkSync; + const isZKSyncChain = technicalStack === ChainTechnicalStack.ZkSync; + const signer = this.multiProvider.getSigner(chain); + const artifact = await getZKSyncArtifactByContractName(contractName); + const contract = await this.multiProvider.handleDeploy( chain, factory, constructorArgs, + artifact, ); if (initializeArgs) { @@ -417,17 +442,18 @@ export abstract class HyperlaneDeployer< `Initializing ${contractName} (${contract.address}) on ${chain}...`, ); + const overrides = this.multiProvider.getTransactionOverrides(chain); + // Estimate gas for the initialize transaction - const estimatedGas = await contract.estimateGas.initialize( - ...initializeArgs, - ); + const estimatedGas = await contract + .connect(signer) + .estimateGas.initialize(...initializeArgs); - // deploy with buffer on gas limit - const overrides = this.multiProvider.getTransactionOverrides(chain); const initTx = await contract.initialize(...initializeArgs, { gasLimit: addBufferToGasLimit(estimatedGas), ...overrides, }); + this.logger.info(`Contract ${contractName} initialized`); const receipt = await this.multiProvider.handleTx(chain, initTx); this.logger.debug( `Successfully initialized ${contractName} (${contract.address}) on ${chain}: ${receipt.transactionHash}`, @@ -435,20 +461,36 @@ export abstract class HyperlaneDeployer< } } - const verificationInput = getContractVerificationInput({ - name: contractName, - contract, - bytecode: factory.bytecode, - expectedimplementation: implementationAddress, - }); + let verificationInput: ContractVerificationInput; + if (isZKSyncChain) { + if (!artifact) { + throw new Error( + `No ZkSync artifact found for contract: ${contractName}`, + ); + } + verificationInput = await getContractVerificationInputForZKSync({ + name: contractName, + contract, + constructorArgs: constructorArgs, + artifact: artifact, + expectedimplementation: implementationAddress, + }); + } else { + verificationInput = getContractVerificationInput({ + name: contractName, + contract, + bytecode: factory.bytecode, + expectedimplementation: implementationAddress, + }); + } + this.addVerificationArtifacts(chain, [verificationInput]); // try verifying contract try { - await this.options.contractVerifier?.verifyContract( - chain, - verificationInput, - ); + await this[ + isZKSyncExplorer ? 'verifyContractForZKSync' : 'verifyContract' + ](chain, verificationInput); } catch (error) { // log error but keep deploying, can also verify post-deployment if needed this.logger.debug(`Error verifying contract: ${error}`); @@ -619,6 +661,9 @@ export abstract class HyperlaneDeployer< chain: ChainName, timelockConfig: UpgradeConfig['timelock'], ): Promise { + const TimelockZkArtifact = await getZKSyncArtifactByContractName( + 'TimelockController', + ); return this.multiProvider.handleDeploy( chain, new TimelockController__factory(), @@ -629,6 +674,7 @@ export abstract class HyperlaneDeployer< [timelockConfig.roles.executor], ethers.constants.AddressZero, ], + TimelockZkArtifact, ); } diff --git a/typescript/sdk/src/deploy/proxy.ts b/typescript/sdk/src/deploy/proxy.ts index 8749e433a9..9e28e4f65a 100644 --- a/typescript/sdk/src/deploy/proxy.ts +++ b/typescript/sdk/src/deploy/proxy.ts @@ -1,4 +1,5 @@ import { ethers } from 'ethers'; +import { Provider as ZKSyncProvider } from 'zksync-ethers'; import { ProxyAdmin__factory } from '@hyperlane-xyz/core'; import { Address, ChainId, eqAddress } from '@hyperlane-xyz/utils'; @@ -7,6 +8,8 @@ import { transferOwnershipTransactions } from '../contracts/contracts.js'; import { AnnotatedEV5Transaction } from '../providers/ProviderType.js'; import { DeployedOwnableConfig } from '../types.js'; +type NetworkProvider = ethers.providers.Provider | ZKSyncProvider; + export type UpgradeConfig = { timelock: { delay: number; @@ -19,7 +22,7 @@ export type UpgradeConfig = { }; export async function proxyImplementation( - provider: ethers.providers.Provider, + provider: NetworkProvider, proxy: Address, ): Promise
{ // Hardcoded storage slot for implementation per EIP-1967 @@ -31,7 +34,7 @@ export async function proxyImplementation( } export async function isInitialized( - provider: ethers.providers.Provider, + provider: NetworkProvider, contract: Address, ): Promise { // Using OZ's Initializable 4.9 which keeps it at the 0x0 slot @@ -43,7 +46,7 @@ export async function isInitialized( } export async function proxyAdmin( - provider: ethers.providers.Provider, + provider: NetworkProvider, proxy: Address, ): Promise
{ // Hardcoded storage slot for admin per EIP-1967 @@ -66,7 +69,7 @@ export function proxyConstructorArgs( } export async function isProxy( - provider: ethers.providers.Provider, + provider: NetworkProvider, proxy: Address, ): Promise { const admin = await proxyAdmin(provider, proxy); diff --git a/typescript/sdk/src/deploy/proxyFactoryUtils.ts b/typescript/sdk/src/deploy/proxyFactoryUtils.ts new file mode 100644 index 0000000000..ecdbd51dd1 --- /dev/null +++ b/typescript/sdk/src/deploy/proxyFactoryUtils.ts @@ -0,0 +1,16 @@ +import { ethers } from 'ethers'; + +import { proxyFactoryFactories } from './contracts.js'; +import { ProxyFactoryFactoriesAddresses } from './types.js'; + +/** + * Creates a default ProxyFactoryFactoriesAddresses object with all values set to ethers.constants.AddressZero. + * @returns {ProxyFactoryFactoriesAddresses} An object with all factory addresses set to AddressZero. + */ +export function createDefaultProxyFactoryFactories(): ProxyFactoryFactoriesAddresses { + const defaultAddress = ethers.constants.AddressZero; + return Object.keys(proxyFactoryFactories).reduce((acc, key) => { + acc[key as keyof ProxyFactoryFactoriesAddresses] = defaultAddress; // Type assertion added here + return acc; + }, {} as ProxyFactoryFactoriesAddresses); +} diff --git a/typescript/sdk/src/deploy/verify/BaseContractVerifier.ts b/typescript/sdk/src/deploy/verify/BaseContractVerifier.ts new file mode 100644 index 0000000000..da0b19b3d5 --- /dev/null +++ b/typescript/sdk/src/deploy/verify/BaseContractVerifier.ts @@ -0,0 +1,207 @@ +import { Logger } from 'pino'; + +import { isZeroishAddress, rootLogger, sleep } from '@hyperlane-xyz/utils'; + +import { ExplorerFamily } from '../../metadata/chainMetadataTypes.js'; +import { MultiProvider } from '../../providers/MultiProvider.js'; +import { ChainName } from '../../types.js'; + +import { + BuildArtifact, + ContractVerificationInput, + SolidityStandardJsonInput, +} from './types.js'; +import { FamilyVerificationDelay } from './utils.js'; + +export abstract class BaseContractVerifier { + protected logger = rootLogger.child({ module: this.constructor.name }); + protected contractSourceMap: { [contractName: string]: string } = {}; + protected readonly standardInputJson: SolidityStandardJsonInput; + + constructor( + protected readonly multiProvider: MultiProvider, + buildArtifact: BuildArtifact, + ) { + this.standardInputJson = buildArtifact.input; + this.createContractSourceMapFromBuildArtifacts(); + } + + protected createContractSourceMapFromBuildArtifacts(): void { + const contractRegex = /contract\s+([A-Z][a-zA-Z0-9]*)/g; + Object.entries(this.standardInputJson.sources).forEach( + ([sourceName, { content }]) => { + const matches = content.matchAll(contractRegex); + for (const match of matches) { + const contractName = match[1]; + if (contractName) { + this.contractSourceMap[contractName] = sourceName; + } + } + }, + ); + } + + public async verifyContract( + chain: ChainName, + input: ContractVerificationInput, + logger = this.logger, + ): Promise { + const verificationLogger = logger.child({ + chain, + name: input.name, + address: input.address, + }); + + if (!this.shouldVerifyContract(chain, input, verificationLogger)) { + return; + } + + const explorerApi = this.multiProvider.tryGetExplorerApi(chain); + + await sleep( + FamilyVerificationDelay[ + explorerApi?.family as keyof typeof FamilyVerificationDelay + ] ?? 0, + ); + await this.verify(chain, input, verificationLogger); + } + + protected shouldVerifyContract( + chain: ChainName, + input: ContractVerificationInput, + verificationLogger: Logger, + ): boolean { + const metadata = this.multiProvider.tryGetChainMetadata(chain); + const rpcUrl = metadata?.rpcUrls[0].http ?? ''; + if (rpcUrl.includes('localhost') || rpcUrl.includes('127.0.0.1')) { + verificationLogger.debug('Skipping verification for local endpoints'); + return false; + } + + const explorerApi = this.multiProvider.tryGetExplorerApi(chain); + if (!explorerApi) { + verificationLogger.debug('No explorer API set, skipping'); + return false; + } + + if (!explorerApi.family) { + verificationLogger.debug(`No explorer family set, skipping`); + return false; + } + + if (explorerApi.family === ExplorerFamily.Other) { + verificationLogger.debug(`Unsupported explorer family, skipping`); + return false; + } + + if (isZeroishAddress(input.address)) return false; + if (Array.isArray(input.constructorArguments)) { + verificationLogger.debug( + 'Constructor arguments in legacy format, skipping', + ); + return false; + } + + return true; + } + + protected abstract verify( + chain: ChainName, + input: ContractVerificationInput, + verificationLogger: Logger, + ): Promise; + + protected getImplementationData( + chain: ChainName, + input: ContractVerificationInput, + verificationLogger: Logger, + ) { + const sourceName = this.contractSourceMap[input.name]; + if (!sourceName) { + const errorMessage = `Contract '${input.name}' not found in provided build artifact`; + verificationLogger.error(errorMessage); + throw new Error(`[${chain}] ${errorMessage}`); + } + + const filteredStandardInputJson = + this.filterStandardInputJsonByContractName( + input.name, + this.standardInputJson, + verificationLogger, + ); + + return this.prepareImplementationData( + sourceName, + input, + filteredStandardInputJson, + ); + } + + protected abstract prepareImplementationData( + sourceName: string, + input: ContractVerificationInput, + filteredStandardInputJson: SolidityStandardJsonInput, + ): any; + + protected filterStandardInputJsonByContractName( + contractName: string, + input: SolidityStandardJsonInput, + verificationLogger: Logger, + ): SolidityStandardJsonInput { + verificationLogger.trace( + { contractName }, + 'Filtering unused contracts from solidity standard input JSON....', + ); + const filteredSources: SolidityStandardJsonInput['sources'] = {}; + const sourceFiles: string[] = Object.keys(input.sources); + const contractFile: string = this.contractSourceMap[contractName]; + const queue: string[] = [contractFile]; + const processed = new Set(); + + while (queue.length > 0) { + const file = queue.shift()!; + if (processed.has(file)) continue; + processed.add(file); + + filteredSources[file] = input.sources[file]; + + const content = input.sources[file].content; + const importStatements = this.getAllImportStatements(content); + + importStatements.forEach((importStatement) => { + const importPath = importStatement.match(/["']([^"']+)["']/)?.[1]; + if (importPath) { + const resolvedPath = this.resolveImportPath(file, importPath); + if (sourceFiles.includes(resolvedPath)) queue.push(resolvedPath); + } + }); + } + + return { + ...input, + sources: filteredSources, + }; + } + + protected getAllImportStatements(content: string): string[] { + const importRegex = + /import\s+(?:(?:(?:"[^"]+"|'[^']+')\s*;)|(?:{[^}]+}\s+from\s+(?:"[^"]+"|'[^']+')\s*;)|(?:\s*(?:"[^"]+"|'[^']+')\s*;))/g; + return content.match(importRegex) || []; + } + + protected resolveImportPath(currentFile: string, importPath: string): string { + if (importPath.startsWith('@') || importPath.startsWith('http')) { + return importPath; + } + const currentDir = currentFile.split('/').slice(0, -1).join('/'); + const resolvedPath = importPath.split('/').reduce((acc, part) => { + if (part === '..') { + acc.pop(); + } else if (part !== '.') { + acc.push(part); + } + return acc; + }, currentDir.split('/')); + return resolvedPath.join('/'); + } +} diff --git a/typescript/sdk/src/deploy/verify/ContractVerifier.ts b/typescript/sdk/src/deploy/verify/ContractVerifier.ts index 49ecd30980..1ccd839d0b 100644 --- a/typescript/sdk/src/deploy/verify/ContractVerifier.ts +++ b/typescript/sdk/src/deploy/verify/ContractVerifier.ts @@ -1,13 +1,14 @@ import fetch from 'cross-fetch'; -import { ethers } from 'ethers'; import { Logger } from 'pino'; +import { buildArtifact as zksyncBuildArtifact } from '@hyperlane-xyz/core/buildArtifact-zksync.js'; import { rootLogger, sleep, strip0x } from '@hyperlane-xyz/utils'; import { ExplorerFamily } from '../../metadata/chainMetadataTypes.js'; import { MultiProvider } from '../../providers/MultiProvider.js'; import { ChainMap, ChainName } from '../../types.js'; +import { BaseContractVerifier } from './BaseContractVerifier.js'; import { BuildArtifact, CompilerOptions, @@ -19,12 +20,8 @@ import { SolidityStandardJsonInput, } from './types.js'; -export class ContractVerifier { +export class ContractVerifier extends BaseContractVerifier { protected logger = rootLogger.child({ module: 'ContractVerifier' }); - - protected contractSourceMap: { [contractName: string]: string } = {}; - - protected readonly standardInputJson: SolidityStandardJsonInput; protected readonly compilerOptions: CompilerOptions; constructor( @@ -33,86 +30,128 @@ export class ContractVerifier { buildArtifact: BuildArtifact, licenseType: CompilerOptions['licenseType'], ) { - this.standardInputJson = buildArtifact.input; - + super(multiProvider, buildArtifact); const compilerversion = `v${buildArtifact.solcLongVersion}`; - - // double check compiler version matches expected format const versionRegex = /v(\d.\d.\d+)\+commit.\w+/; const matches = versionRegex.exec(compilerversion); if (!matches) { throw new Error(`Invalid compiler version ${compilerversion}`); } - - // set compiler options - // only license type is configurable, empty if not provided this.compilerOptions = { codeformat: 'solidity-standard-json-input', compilerversion, licenseType, }; - - // process input to create mapping of contract names to source names - // this is required to construct the fully qualified contract name - const contractRegex = /contract\s+([A-Z][a-zA-Z0-9]*)/g; - Object.entries(buildArtifact.input.sources).forEach( - ([sourceName, { content }]) => { - const matches = content.matchAll(contractRegex); - for (const match of matches) { - const contractName = match[1]; - if (contractName) { - this.contractSourceMap[contractName] = sourceName; - } - } - }, - ); + if (zksyncBuildArtifact?.zk_version) + this.compilerOptions.zksolcversion = `v${zksyncBuildArtifact.zk_version}`; } - public async verifyContract( + protected async verify( chain: ChainName, input: ContractVerificationInput, - logger = this.logger, + verificationLogger: Logger, ): Promise { - const verificationLogger = logger.child({ - chain, - name: input.name, - address: input.address, - }); + const contractType: string = input.isProxy ? 'proxy' : 'implementation'; - const metadata = this.multiProvider.tryGetChainMetadata(chain); - const rpcUrl = metadata?.rpcUrls[0].http ?? ''; - if (rpcUrl.includes('localhost') || rpcUrl.includes('127.0.0.1')) { - verificationLogger.debug('Skipping verification for local endpoints'); - return; - } + verificationLogger.debug(`📝 Verifying ${contractType}...`); - const explorerApi = this.multiProvider.tryGetExplorerApi(chain); - if (!explorerApi) { - verificationLogger.debug('No explorer API set, skipping'); - return; - } + const data = input.isProxy + ? this.getProxyData(input) + : this.getImplementationData(chain, input, verificationLogger); - if (!explorerApi.family) { - verificationLogger.debug(`No explorer family set, skipping`); - return; - } + try { + const guid: string = await this.submitForm( + chain, + input.isProxy + ? ExplorerApiActions.VERIFY_PROXY + : ExplorerApiActions.VERIFY_IMPLEMENTATION, + verificationLogger, + data, + ); - if (explorerApi.family === ExplorerFamily.Other) { - verificationLogger.debug(`Unsupported explorer family, skipping`); - return; - } + verificationLogger.trace( + { guid }, + `Retrieved guid from verified ${contractType}.`, + ); + + await this.checkStatus( + chain, + input, + verificationLogger, + guid, + contractType, + ); + + const addressUrl = await this.multiProvider.tryGetExplorerAddressUrl( + chain, + input.address, + ); - if (input.address === ethers.constants.AddressZero) return; - if (Array.isArray(input.constructorArguments)) { verificationLogger.debug( - 'Constructor arguments in legacy format, skipping', + { + addressUrl: addressUrl + ? `${addressUrl}#code` + : `Could not retrieve ${contractType} explorer URL.`, + }, + `✅ Successfully verified ${contractType}.`, + ); + } catch (error) { + verificationLogger.debug( + { error }, + `Verification of ${contractType} failed`, ); - return; + throw error; } + } - await this.verify(chain, input, verificationLogger); + private async checkStatus( + chain: ChainName, + input: ContractVerificationInput, + verificationLogger: Logger, + guid: string, + contractType: string, + ): Promise { + verificationLogger.trace({ guid }, `Checking ${contractType} status...`); + await this.submitForm( + chain, + input.isProxy + ? ExplorerApiActions.CHECK_PROXY_STATUS + : ExplorerApiActions.CHECK_IMPLEMENTATION_STATUS, + verificationLogger, + { + guid: guid, + }, + ); + } + + private getProxyData(input: ContractVerificationInput) { + return { + address: input.address, + expectedimplementation: input.expectedimplementation, + }; } + protected prepareImplementationData( + sourceName: string, + input: ContractVerificationInput, + filteredStandardInputJson: SolidityStandardJsonInput, + ) { + return { + sourceCode: JSON.stringify(filteredStandardInputJson), + contractname: `${sourceName}:${input.name}`, + contractaddress: input.address, + constructorArguements: strip0x(input.constructorArguments ?? ''), + ...this.compilerOptions, + }; + } + + /** + * @notice Submits the verification form to the explorer API + * @param chain The name of the chain where the contract is deployed + * @param verificationLogger A logger instance for verification-specific logging + * @param options Additional options for the API request + * @returns The response from the explorer API + */ private async submitForm( chain: ChainName, action: ExplorerApiActions, @@ -125,7 +164,6 @@ export class ContractVerifier { apiKey = this.apiKeys[chain], } = this.multiProvider.getExplorerApi(chain); const params = new URLSearchParams(); - params.set('module', 'contract'); params.set('action', action); if (apiKey) params.set('apikey', apiKey); @@ -140,6 +178,7 @@ export class ContractVerifier { if (isGetRequest) url.search = params.toString(); switch (family) { + case ExplorerFamily.ZkSync: case ExplorerFamily.Etherscan: timeout = 5000; break; @@ -256,203 +295,4 @@ export class ContractVerifier { await sleep(timeout); return responseJson.result; } - - private async verify( - chain: ChainName, - input: ContractVerificationInput, - verificationLogger: Logger, - ): Promise { - const contractType: string = input.isProxy ? 'proxy' : 'implementation'; - - verificationLogger.debug(`📝 Verifying ${contractType}...`); - - const data = input.isProxy - ? this.getProxyData(input) - : this.getImplementationData(chain, input, verificationLogger); - - try { - const guid: string = await this.submitForm( - chain, - input.isProxy - ? ExplorerApiActions.VERIFY_PROXY - : ExplorerApiActions.VERIFY_IMPLEMENTATION, - verificationLogger, - data, - ); - - verificationLogger.trace( - { guid }, - `Retrieved guid from verified ${contractType}.`, - ); - - await this.checkStatus( - chain, - input, - verificationLogger, - guid, - contractType, - ); - - const addressUrl = await this.multiProvider.tryGetExplorerAddressUrl( - chain, - input.address, - ); - - verificationLogger.debug( - { - addressUrl: addressUrl - ? `${addressUrl}#code` - : `Could not retrieve ${contractType} explorer URL.`, - }, - `✅ Successfully verified ${contractType}.`, - ); - } catch (error) { - verificationLogger.debug( - { error }, - `Verification of ${contractType} failed`, - ); - throw error; - } - } - - private async checkStatus( - chain: ChainName, - input: ContractVerificationInput, - verificationLogger: Logger, - guid: string, - contractType: string, - ): Promise { - verificationLogger.trace({ guid }, `Checking ${contractType} status...`); - await this.submitForm( - chain, - input.isProxy - ? ExplorerApiActions.CHECK_PROXY_STATUS - : ExplorerApiActions.CHECK_IMPLEMENTATION_STATUS, - verificationLogger, - { - guid: guid, - }, - ); - } - - private getProxyData(input: ContractVerificationInput) { - return { - address: input.address, - expectedimplementation: input.expectedimplementation, - }; - } - - private getImplementationData( - chain: ChainName, - input: ContractVerificationInput, - verificationLogger: Logger, - ) { - const sourceName = this.contractSourceMap[input.name]; - if (!sourceName) { - const errorMessage = `Contract '${input.name}' not found in provided build artifact`; - verificationLogger.error(errorMessage); - throw new Error(`[${chain}] ${errorMessage}`); - } - - const filteredStandardInputJson = - this.filterStandardInputJsonByContractName( - input.name, - this.standardInputJson, - verificationLogger, - ); - - return { - sourceCode: JSON.stringify(filteredStandardInputJson), - contractname: `${sourceName}:${input.name}`, - contractaddress: input.address, - /* TYPO IS ENFORCED BY API */ - constructorArguements: strip0x(input.constructorArguments ?? ''), - ...this.compilerOptions, - }; - } - - /** - * Filters the solidity standard input for a specific contract name. - * - * This is a BFS impl to traverse the source input dependency graph. - * 1. Named contract file is set as root node. - * 2. The next level is formed by the direct imports of the contract file. - * 3. Each subsequent level's dependencies form the next level, etc. - * 4. The queue tracks the next files to process, and ensures the dependency graph explorered level by level. - */ - private filterStandardInputJsonByContractName( - contractName: string, - input: SolidityStandardJsonInput, - verificationLogger: Logger, - ): SolidityStandardJsonInput { - verificationLogger.trace( - { contractName }, - 'Filtering unused contracts from solidity standard input JSON....', - ); - const filteredSources: SolidityStandardJsonInput['sources'] = {}; - const sourceFiles: string[] = Object.keys(input.sources); - const contractFile: string = this.getContractFile( - contractName, - sourceFiles, - ); - const queue: string[] = [contractFile]; - const processed = new Set(); - - while (queue.length > 0) { - const file = queue.shift()!; - if (processed.has(file)) continue; - processed.add(file); - - filteredSources[file] = input.sources[file]; - - const content = input.sources[file].content; - const importStatements = this.getAllImportStatements(content); - - importStatements.forEach((importStatement) => { - const importPath = importStatement.match(/["']([^"']+)["']/)?.[1]; - if (importPath) { - const resolvedPath = this.resolveImportPath(file, importPath); - if (sourceFiles.includes(resolvedPath)) queue.push(resolvedPath); - } - }); - } - - return { - ...input, - sources: filteredSources, - }; - } - - private getContractFile(contractName: string, sourceFiles: string[]): string { - const contractFile = sourceFiles.find((file) => - file.endsWith(`/${contractName}.sol`), - ); - if (!contractFile) { - throw new Error(`Contract ${contractName} not found in sources.`); - } - return contractFile; - } - - private getAllImportStatements(content: string) { - const importRegex = - /import\s+(?:(?:(?:"[^"]+"|'[^']+')\s*;)|(?:{[^}]+}\s+from\s+(?:"[^"]+"|'[^']+')\s*;)|(?:\s*(?:"[^"]+"|'[^']+')\s*;))/g; - return content.match(importRegex) || []; - } - - private resolveImportPath(currentFile: string, importPath: string): string { - /* Use as-is for external dependencies and absolute imports */ - if (importPath.startsWith('@') || importPath.startsWith('http')) { - return importPath; - } - const currentDir = currentFile.split('/').slice(0, -1).join('/'); - const resolvedPath = importPath.split('/').reduce((acc, part) => { - if (part === '..') { - acc.pop(); - } else if (part !== '.') { - acc.push(part); - } - return acc; - }, currentDir.split('/')); - return resolvedPath.join('/'); - } } diff --git a/typescript/sdk/src/deploy/verify/PostDeploymentContractVerifier.ts b/typescript/sdk/src/deploy/verify/PostDeploymentContractVerifier.ts index b657b8e7e6..bd2cb71112 100644 --- a/typescript/sdk/src/deploy/verify/PostDeploymentContractVerifier.ts +++ b/typescript/sdk/src/deploy/verify/PostDeploymentContractVerifier.ts @@ -5,14 +5,16 @@ import { MultiProvider } from '../../providers/MultiProvider.js'; import { ChainMap } from '../../types.js'; import { MultiGeneric } from '../../utils/MultiGeneric.js'; +import { BaseContractVerifier } from './BaseContractVerifier.js'; import { ContractVerifier } from './ContractVerifier.js'; +import { ZKSyncContractVerifier } from './ZKSyncContractVerifier.js'; import { BuildArtifact, CompilerOptions, VerificationInput } from './types.js'; export class PostDeploymentContractVerifier extends MultiGeneric { protected logger = rootLogger.child({ module: 'PostDeploymentContractVerifier', }); - protected readonly contractVerifier: ContractVerifier; + protected contractVerifier: BaseContractVerifier; constructor( verificationInputs: ChainMap, @@ -35,6 +37,14 @@ export class PostDeploymentContractVerifier extends MultiGeneric { // can check explorer family here to avoid doing these checks for each input in verifier const { family } = this.multiProvider.getExplorerApi(chain); + + if (family === ExplorerFamily.ZkSync) { + this.logger.debug('Using ZkSync verifier'); + this.contractVerifier = new ZKSyncContractVerifier( + this.multiProvider, + ); + } + if (family === ExplorerFamily.Other) { this.logger.warn( `Skipping verification for ${chain} due to unsupported explorer family.`, diff --git a/typescript/sdk/src/deploy/verify/ZKSyncContractVerifier.ts b/typescript/sdk/src/deploy/verify/ZKSyncContractVerifier.ts new file mode 100644 index 0000000000..4110d1f5e2 --- /dev/null +++ b/typescript/sdk/src/deploy/verify/ZKSyncContractVerifier.ts @@ -0,0 +1,158 @@ +import fetch from 'cross-fetch'; +import { Logger } from 'pino'; + +import { buildArtifact } from '@hyperlane-xyz/core/buildArtifact-zksync.js'; +import { rootLogger } from '@hyperlane-xyz/utils'; + +import { MultiProvider } from '../../providers/MultiProvider.js'; +import { ChainName } from '../../types.js'; + +import { BaseContractVerifier } from './BaseContractVerifier.js'; +import { + BuildArtifact, + ContractVerificationInput, + SolidityStandardJsonInput, + ZKSyncCompilerOptions, +} from './types.js'; + +/** + * @title ZKSyncContractVerifier + * @notice Handles the verification of ZkSync contracts on block explorers + * @dev This class manages the process of verifying ZkSync contracts, including + * preparing verification data and submitting it to the appropriate explorer API + * Note: Etherscan verification is managed by the ContractVerifier class + * Blockscout verification is not currently supported on ZkSync + */ +export class ZKSyncContractVerifier extends BaseContractVerifier { + protected logger = rootLogger.child({ module: 'ZKSyncContractVerifier' }); + + protected readonly standardInputJson: SolidityStandardJsonInput; + protected readonly compilerOptions: ZKSyncCompilerOptions; + + /** + * @notice Creates a new ZKSyncContractVerifier instance + * @param multiProvider An instance of MultiProvider for interacting with multiple chains + */ + constructor(protected readonly multiProvider: MultiProvider) { + super(multiProvider, buildArtifact); + this.standardInputJson = (buildArtifact as BuildArtifact).input; + + const compilerZksolcVersion = `v${ + (buildArtifact as { zk_version: string }).zk_version + }`; + const compilerSolcVersion = (buildArtifact as BuildArtifact) + .solcLongVersion; + + this.compilerOptions = { + codeFormat: 'solidity-standard-json-input', + compilerSolcVersion, + compilerZksolcVersion, + optimizationUsed: true, + }; + } + + /** + * @notice Verifies a contract on the specified chain + * @param chain The name of the chain where the contract is deployed + * @param input The contract verification input data + * @param verificationLogger A logger instance for verification-specific logging + */ + protected async verify( + chain: ChainName, + input: ContractVerificationInput, + verificationLogger: Logger, + ): Promise { + const contractType: string = input.isProxy ? 'proxy' : 'implementation'; + + verificationLogger.debug(`📝 Verifying ${contractType}...`); + + const data = this.getImplementationData(chain, input, verificationLogger); + + try { + const verificationId: string = await this.submitForm( + chain, + verificationLogger, + data, + ); + + verificationLogger.trace( + { verificationId }, + `Retrieved verificationId from verified ${contractType}.`, + ); + } catch (error) { + verificationLogger.debug( + { error }, + `Verification of ${contractType} failed`, + ); + throw error; + } + } + + protected prepareImplementationData( + sourceName: string, + input: ContractVerificationInput, + filteredStandardInputJson: SolidityStandardJsonInput, + ) { + return { + sourceCode: filteredStandardInputJson, + contractName: `${sourceName}:${input.name}`, + contractAddress: input.address, + constructorArguments: `0x${input.constructorArguments || ''}`, + ...this.compilerOptions, + }; + } + + /** + * @notice Submits the verification form to the explorer API + * @param chain The name of the chain where the contract is deployed + * @param verificationLogger A logger instance for verification-specific logging + * @param options Additional options for the API request + * @returns The response from the explorer API + */ + private async submitForm( + chain: ChainName, + verificationLogger: Logger, + options?: Record, + ): Promise { + const { apiUrl, family } = this.multiProvider.getExplorerApi(chain); + + const url = new URL(apiUrl); + verificationLogger.trace( + { apiUrl, chain }, + 'Sending request to explorer...', + ); + + const response = await fetch(url.toString(), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(options), + }); + let responseJson; + try { + responseJson = await response.json(); + verificationLogger.trace( + { apiUrl, chain }, + 'Parsing response from explorer...', + ); + } catch (error) { + verificationLogger.trace( + { + error, + failure: response.statusText, + status: response.status, + chain, + apiUrl, + family, + }, + 'Failed to parse response from explorer.', + ); + throw new Error( + `Failed to parse response from explorer (${apiUrl}, ${chain}): ${ + response.statusText || 'UNKNOWN STATUS TEXT' + } (${response.status || 'UNKNOWN STATUS'})`, + ); + } + + return responseJson; + } +} diff --git a/typescript/sdk/src/deploy/verify/types.ts b/typescript/sdk/src/deploy/verify/types.ts index 3ef1dd9cd3..585359edae 100644 --- a/typescript/sdk/src/deploy/verify/types.ts +++ b/typescript/sdk/src/deploy/verify/types.ts @@ -27,6 +27,7 @@ export type SolidityStandardJsonInput = { export type BuildArtifact = { input: SolidityStandardJsonInput; solcLongVersion: string; + zk_version?: string; //only for zksync }; // see https://etherscan.io/contract-license-types @@ -51,6 +52,14 @@ export type CompilerOptions = { codeformat: 'solidity-standard-json-input'; compilerversion: string; // see https://etherscan.io/solcversions for list of support versions licenseType?: ExplorerLicenseType; + zksolcversion?: string; //only for zksync chains +}; + +export type ZKSyncCompilerOptions = { + codeFormat: 'solidity-standard-json-input'; + compilerSolcVersion: string; + compilerZksolcVersion: string; + optimizationUsed: boolean; }; export enum ExplorerApiActions { diff --git a/typescript/sdk/src/deploy/verify/utils.ts b/typescript/sdk/src/deploy/verify/utils.ts index 85f5877bfc..490b963bfd 100644 --- a/typescript/sdk/src/deploy/verify/utils.ts +++ b/typescript/sdk/src/deploy/verify/utils.ts @@ -1,8 +1,10 @@ import { ethers, utils } from 'ethers'; +import { Hex, decodeFunctionData, parseAbi } from 'viem'; import { ProxyAdmin__factory, TransparentUpgradeableProxy__factory, + ZKSyncArtifact, } from '@hyperlane-xyz/core'; import { Address, assert, eqAddress } from '@hyperlane-xyz/utils'; @@ -71,6 +73,36 @@ export function getContractVerificationInput({ ); } +export async function getContractVerificationInputForZKSync({ + name, + contract, + constructorArgs, + artifact, + isProxy, + expectedimplementation, +}: { + name: string; + contract: ethers.Contract; + constructorArgs: any[]; + artifact: ZKSyncArtifact; + isProxy?: boolean; + expectedimplementation?: Address; +}): Promise { + const args = encodeArguments(artifact.abi, constructorArgs); + return buildVerificationInput( + name, + contract.address, + args, + isProxy, + expectedimplementation, + ); +} + +export function encodeArguments(abi: any, constructorArgs: any[]): string { + const contractInterface = new utils.Interface(abi); + return contractInterface.encodeDeploy(constructorArgs).replace('0x', ''); +} + /** * Check if the artifact should be added to the verification inputs. * @param verificationInputs - The verification inputs for the chain. @@ -93,7 +125,14 @@ export function shouldAddVerificationInput( } /** - * Retrieves the constructor args using their respective Explorer and/or RPC (eth_getTransactionByHash) + * @notice Defines verification delay times for different blockchain explorer families. + * @dev This constant object associates explorer families with specific delay times (in milliseconds) + */ +export const FamilyVerificationDelay = { + [ExplorerFamily.Etherscan]: 40000, +} as const; + +/** Retrieves the constructor args using their respective Explorer and/or RPC (eth_getTransactionByHash) */ export async function getConstructorArgumentsApi({ chainName, @@ -119,6 +158,13 @@ export async function getConstructorArgumentsApi({ multiProvider, }); break; + case ExplorerFamily.ZkSync: + constructorArgs = await getZKSyncConstructorArgs({ + chainName, + contractAddress, + multiProvider, + }); + break; case ExplorerFamily.Blockscout: constructorArgs = await getBlockScoutConstructorArgs({ chainName, @@ -133,20 +179,19 @@ export async function getConstructorArgumentsApi({ return constructorArgs; } -export async function getEtherscanConstructorArgs({ - bytecode, +async function getConstructorArgsFromExplorer({ chainName, + blockExplorerApiKey, + blockExplorerApiUrl, contractAddress, multiProvider, }: { - bytecode: string; + blockExplorerApiKey?: string; + blockExplorerApiUrl: string; chainName: string; contractAddress: Address; multiProvider: MultiProvider; -}): Promise { - const { apiUrl: blockExplorerApiUrl, apiKey: blockExplorerApiKey } = - multiProvider.getExplorerApi(chainName); - +}) { const url = new URL(blockExplorerApiUrl); url.searchParams.append('module', 'contract'); url.searchParams.append('action', 'getcontractcreation'); @@ -176,11 +221,69 @@ export async function getEtherscanConstructorArgs({ }), }); + return creationTxResp.json(); +} + +export async function getEtherscanConstructorArgs({ + bytecode, + chainName, + contractAddress, + multiProvider, +}: { + bytecode: string; + chainName: string; + contractAddress: Address; + multiProvider: MultiProvider; +}): Promise { + const { apiUrl: blockExplorerApiUrl, apiKey: blockExplorerApiKey } = + multiProvider.getExplorerApi(chainName); + // Truncate the deployment bytecode - const creationInput: string = (await creationTxResp.json()).result.input; + const creationTxResp = await getConstructorArgsFromExplorer({ + chainName, + blockExplorerApiKey, + blockExplorerApiUrl, + contractAddress, + multiProvider, + }); + const creationInput: string = creationTxResp.result.input; return creationInput.substring(bytecode.length); } +export async function getZKSyncConstructorArgs({ + chainName, + contractAddress, + multiProvider, +}: { + chainName: string; + contractAddress: Address; + multiProvider: MultiProvider; +}): Promise { + const { apiUrl, apiKey: blockExplorerApiKey } = + multiProvider.getExplorerApi(chainName); + + // Create the API URL using Registry blockExplorers.apiUrl + // Assumes that ZkSync uses something like `https://zero-network.calderaexplorer.xyz/verification/contract_verification`. + const blockExplorerApiUrl = new URL('/api', new URL(apiUrl).origin).href; + + // Truncate the deployment bytecode + const creationTxResp = await getConstructorArgsFromExplorer({ + chainName, + blockExplorerApiKey, + blockExplorerApiUrl, + contractAddress, + multiProvider, + }); + const creationInput: string = creationTxResp.result.input; + + const res = decodeFunctionData({ + abi: parseAbi(['function create(bytes32,bytes32,bytes)']), + data: creationInput as Hex, + }); + + return res.args[2].replace('0x', ''); +} + export async function getBlockScoutConstructorArgs({ chainName, contractAddress, diff --git a/typescript/sdk/src/hook/EvmHookModule.ts b/typescript/sdk/src/hook/EvmHookModule.ts index 6a7cfbbd3e..05db6ae722 100644 --- a/typescript/sdk/src/hook/EvmHookModule.ts +++ b/typescript/sdk/src/hook/EvmHookModule.ts @@ -64,6 +64,7 @@ import { HookConfig, HookConfigSchema, HookType, + HookTypeToContractNameMap, IgpHookConfig, MUTABLE_HOOK_TYPE, OpStackHookConfig, @@ -931,9 +932,10 @@ export class EvmHookModule extends HyperlaneModule< // deploy fallback hook const fallbackHook = await this.deploy({ config: config.fallback }); // deploy routing hook with fallback - routingHook = await this.deployer.deployContract( + routingHook = await this.deployer.deployContractWithName( this.chain, HookType.FALLBACK_ROUTING, + HookTypeToContractNameMap[HookType.FALLBACK_ROUTING], [this.args.addresses.mailbox, deployerAddress, fallbackHook.address], ); } else { diff --git a/typescript/sdk/src/hook/types.ts b/typescript/sdk/src/hook/types.ts index 76e749fd95..8f1ea0c7c4 100644 --- a/typescript/sdk/src/hook/types.ts +++ b/typescript/sdk/src/hook/types.ts @@ -38,6 +38,21 @@ export enum HookType { ARB_L2_TO_L1 = 'arbL2ToL1Hook', } +export const HookTypeToContractNameMap: Record< + Exclude, + string +> = { + [HookType.MERKLE_TREE]: 'merkleTreeHook', + [HookType.INTERCHAIN_GAS_PAYMASTER]: 'interchainGasPaymaster', + [HookType.AGGREGATION]: 'staticAggregationHook', + [HookType.PROTOCOL_FEE]: 'protocolFee', + [HookType.OP_STACK]: 'opStackHook', + [HookType.ROUTING]: 'domainRoutingHook', + [HookType.FALLBACK_ROUTING]: 'fallbackDomainRoutingHook', + [HookType.PAUSABLE]: 'pausableHook', + [HookType.ARB_L2_TO_L1]: 'arbL2ToL1Hook', +}; + export type MerkleTreeHookConfig = z.infer; export type IgpHookConfig = z.infer; export type ProtocolFeeHookConfig = z.infer; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 0ec8e740b3..0e1cfbb4dd 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -107,6 +107,7 @@ export { ViolationType, } from './deploy/types.js'; export { ContractVerifier } from './deploy/verify/ContractVerifier.js'; +export { ZKSyncContractVerifier } from './deploy/verify/ZKSyncContractVerifier.js'; export { PostDeploymentContractVerifier } from './deploy/verify/PostDeploymentContractVerifier.js'; export { BuildArtifact, @@ -205,7 +206,13 @@ export { WeightedMultisigIsmConfig, WeightedMultisigIsmConfigSchema, } from './ism/types.js'; -export { collectValidators, moduleCanCertainlyVerify } from './ism/utils.js'; +export { + collectValidators, + moduleCanCertainlyVerify, + isStaticDeploymentSupported, + isIsmCompatible, + isStaticIsm, +} from './ism/utils.js'; export { AgentChainMetadata, AgentChainMetadataSchema, diff --git a/typescript/sdk/src/ism/EvmIsmModule.ts b/typescript/sdk/src/ism/EvmIsmModule.ts index 310b6e1ee9..a3c8a9226a 100644 --- a/typescript/sdk/src/ism/EvmIsmModule.ts +++ b/typescript/sdk/src/ism/EvmIsmModule.ts @@ -74,6 +74,7 @@ export class EvmIsmModule extends HyperlaneModule< this.ismFactory = HyperlaneIsmFactory.fromAddressesMap( { [params.chain]: params.addresses }, multiProvider, + contractVerifier, ); this.mailbox = params.addresses.mailbox; diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index 515a2da0a9..7d9d9c63de 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -25,6 +25,7 @@ import { StorageMessageIdMultisigIsm__factory, TestIsm__factory, TrustedRelayerIsm__factory, + ZKSyncArtifact, } from '@hyperlane-xyz/core'; import { Address, @@ -47,8 +48,10 @@ import { ProxyFactoryFactories, proxyFactoryFactories, } from '../deploy/contracts.js'; +import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainMap, ChainName } from '../types.js'; +import { getZKSyncArtifactByContractName } from '../utils/zksync.js'; import { AggregationIsmConfig, @@ -62,7 +65,7 @@ import { RoutingIsmDelta, WeightedMultisigIsmConfig, } from './types.js'; -import { routingModuleDelta } from './utils.js'; +import { isIsmCompatible, routingModuleDelta } from './utils.js'; const ismFactories = { [IsmType.PAUSABLE]: new PausableIsm__factory(), @@ -90,25 +93,33 @@ export class HyperlaneIsmFactory extends HyperlaneApp { constructor( contractsMap: HyperlaneContractsMap, public readonly multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, ) { super( contractsMap, multiProvider, rootLogger.child({ module: 'ismFactoryApp' }), ); - this.deployer = new IsmDeployer(multiProvider, ismFactories); + this.deployer = new IsmDeployer(multiProvider, ismFactories, { + contractVerifier, + }); } static fromAddressesMap( addressesMap: HyperlaneAddressesMap, multiProvider: MultiProvider, + contractVerifier?: ContractVerifier, ): HyperlaneIsmFactory { const helper = appFromAddressesMapHelper( addressesMap, proxyFactoryFactories, multiProvider, ); - return new HyperlaneIsmFactory(helper.contractsMap, multiProvider); + return new HyperlaneIsmFactory( + helper.contractsMap, + multiProvider, + contractVerifier, + ); } async deploy(params: { @@ -136,6 +147,14 @@ export class HyperlaneIsmFactory extends HyperlaneApp { }`, ); + const { technicalStack } = this.multiProvider.getChainMetadata(destination); + + // For static ISM types it checks whether the technical stack supports static contract deployment + assert( + isIsmCompatible({ chainTechnicalStack: technicalStack, ismType }), + `Technical stack ${technicalStack} is not compatible with ${ismType}`, + ); + let contract: DeployedIsmType[typeof ismType]; switch (ismType) { case IsmType.MESSAGE_ID_MULTISIG: @@ -250,11 +269,13 @@ export class HyperlaneIsmFactory extends HyperlaneApp { factory: | StorageMerkleRootMultisigIsm__factory | StorageMessageIdMultisigIsm__factory, + artifact: ZKSyncArtifact | undefined, ) => { const contract = await this.multiProvider.handleDeploy( destination, factory, [config.validators, config.threshold], + artifact, ); return contract.address; }; @@ -275,11 +296,13 @@ export class HyperlaneIsmFactory extends HyperlaneApp { case IsmType.STORAGE_MERKLE_ROOT_MULTISIG: address = await deployStorage( new StorageMerkleRootMultisigIsm__factory(), + await getZKSyncArtifactByContractName('StorageMerkleRootMultisigIsm'), ); break; case IsmType.STORAGE_MESSAGE_ID_MULTISIG: address = await deployStorage( new StorageMessageIdMultisigIsm__factory(), + await getZKSyncArtifactByContractName('StorageMessageIdMultisigIsm'), ); break; default: @@ -467,6 +490,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp { destination, new DefaultFallbackRoutingIsm__factory(), [mailbox], + await getZKSyncArtifactByContractName('DefaultFallbackRoutingIsm'), ); // TODO: Should verify contract here logger.debug('Initialising fallback routing ISM ...'); diff --git a/typescript/sdk/src/ism/types.ts b/typescript/sdk/src/ism/types.ts index 87adb2cff2..810f6ada33 100644 --- a/typescript/sdk/src/ism/types.ts +++ b/typescript/sdk/src/ism/types.ts @@ -67,6 +67,16 @@ export const MUTABLE_ISM_TYPE = [ IsmType.PAUSABLE, ]; +// ISM types that require static deployment +export const STATIC_ISM_TYPES = [ + IsmType.AGGREGATION, + IsmType.MERKLE_ROOT_MULTISIG, + IsmType.MESSAGE_ID_MULTISIG, + IsmType.WEIGHTED_MERKLE_ROOT_MULTISIG, + IsmType.WEIGHTED_MESSAGE_ID_MULTISIG, + IsmType.ICA_ROUTING, +]; + // mapping between the two enums export function ismTypeToModuleType(ismType: IsmType): ModuleType { switch (ismType) { diff --git a/typescript/sdk/src/ism/utils.ts b/typescript/sdk/src/ism/utils.ts index 5989854ab7..6fceeb69ec 100644 --- a/typescript/sdk/src/ism/utils.ts +++ b/typescript/sdk/src/ism/utils.ts @@ -24,6 +24,7 @@ import { import { HyperlaneContracts } from '../contracts/types.js'; import { ProxyFactoryFactories } from '../deploy/contracts.js'; +import { ChainTechnicalStack } from '../metadata/chainMetadataTypes.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { ChainName } from '../types.js'; @@ -34,6 +35,7 @@ import { ModuleType, RoutingIsmConfig, RoutingIsmDelta, + STATIC_ISM_TYPES, ismTypeToModuleType, } from './types.js'; @@ -534,3 +536,47 @@ export function collectValidators( return new Set(validators); } + +/** + * Checks if the given ISM type requires static deployment + * + * @param {IsmType} ismType - The type of Interchain Security Module (ISM) + * @returns {boolean} True if the ISM type requires static deployment, false otherwise + */ +export function isStaticIsm(ismType: IsmType): boolean { + return STATIC_ISM_TYPES.includes(ismType); +} + +/** + * Determines if static ISM deployment is supported on a given chain's technical stack + * @dev Currently, only ZkSync does not support static deployments + * @param chainTechnicalStack - The technical stack of the target chain + * @returns boolean - true if static deployment is supported, false for ZkSync + */ +export function isStaticDeploymentSupported( + chainTechnicalStack: ChainTechnicalStack | undefined, +): boolean { + if (chainTechnicalStack === undefined) return true; + return chainTechnicalStack !== ChainTechnicalStack.ZkSync; +} + +/** + * Checks if the given ISM type is compatible with the chain's technical stack. + * + * @param {Object} params - The parameters object + * @param {ChainTechnicalStack | undefined} params.chainTechnicalStack - The technical stack of the chain + * @param {IsmType} params.ismType - The type of Interchain Security Module (ISM) + * @returns {boolean} True if the ISM type is compatible with the chain, false otherwise + */ +export function isIsmCompatible({ + chainTechnicalStack, + ismType, +}: { + chainTechnicalStack: ChainTechnicalStack | undefined; + ismType: IsmType; +}): boolean { + // Skip compatibility check for non-static ISMs as they're always supported + if (!isStaticIsm(ismType)) return true; + + return isStaticDeploymentSupported(chainTechnicalStack); +} diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index f8972f9bdb..5f57159487 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -22,6 +22,7 @@ export enum ExplorerFamily { Etherscan = 'etherscan', Blockscout = 'blockscout', Routescan = 'routescan', + ZkSync = 'zksync', Other = 'other', } diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index 7b5f923061..544cc7102d 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -8,7 +8,13 @@ import { providers, } from 'ethers'; import { Logger } from 'pino'; +import { + ContractFactory as ZKSyncContractFactory, + Provider as ZKSyncProvider, + Wallet as ZKSyncWallet, +} from 'zksync-ethers'; +import { ZKSyncArtifact } from '@hyperlane-xyz/core'; import { Address, addBufferToGasLimit, @@ -18,13 +24,18 @@ import { import { testChainMetadata, testChains } from '../consts/testChains.js'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; -import { ChainMetadata } from '../metadata/chainMetadataTypes.js'; +import { + ChainMetadata, + ChainTechnicalStack, +} from '../metadata/chainMetadataTypes.js'; import { ChainMap, ChainName, ChainNameOrId } from '../types.js'; +import { ZKSyncDeployer } from '../zksync/ZKSyncDeployer.js'; import { AnnotatedEV5Transaction } from './ProviderType.js'; import { ProviderBuilderFn, defaultProviderBuilder, + defaultZKProviderBuilder, } from './providerBuilders.js'; type Provider = providers.Provider; @@ -89,17 +100,25 @@ export class MultiProvider extends ChainMetadataManager { tryGetProvider(chainNameOrId: ChainNameOrId): Provider | null { const metadata = this.tryGetChainMetadata(chainNameOrId); if (!metadata) return null; - const { name, chainId, rpcUrls } = metadata; + const { name, chainId, rpcUrls, technicalStack } = metadata; if (this.providers[name]) return this.providers[name]; if (testChains.includes(name)) { - this.providers[name] = new providers.JsonRpcProvider( - 'http://127.0.0.1:8545', - 31337, - ); + if (technicalStack === ChainTechnicalStack.ZkSync) { + this.providers[name] = new ZKSyncProvider('http://127.0.0.1:8011', 260); + } else { + this.providers[name] = new providers.JsonRpcProvider( + 'http://127.0.0.1:8545', + 31337, + ); + } } else if (rpcUrls.length) { - this.providers[name] = this.providerBuilder(rpcUrls, chainId); + if (technicalStack === ChainTechnicalStack.ZkSync) { + this.providers[name] = defaultZKProviderBuilder(rpcUrls, chainId); + } else { + this.providers[name] = this.providerBuilder(rpcUrls, chainId); + } } else { return null; } @@ -155,7 +174,8 @@ export class MultiProvider extends ChainMetadataManager { if (signer.provider) return signer; // Auto-connect the signer for convenience const provider = this.tryGetProvider(chainName); - return provider ? signer.connect(provider) : signer; + if (!provider) return signer; + return signer.connect(provider); } /** @@ -308,34 +328,53 @@ export class MultiProvider extends ChainMetadataManager { * Wait for deploy tx to be confirmed * @throws if chain's metadata or signer has not been set or tx fails */ - async handleDeploy( + async handleDeploy( chainNameOrId: ChainNameOrId, factory: F, params: Parameters, + artifact?: ZKSyncArtifact, ): Promise>> { // setup contract factory const overrides = this.getTransactionOverrides(chainNameOrId); const signer = this.getSigner(chainNameOrId); - const contractFactory = await factory.connect(signer); - // estimate gas - const deployTx = contractFactory.getDeployTransaction(...params); - const gasEstimated = await signer.estimateGas(deployTx); + const metadata = this.getChainMetadata(chainNameOrId); + const { technicalStack } = metadata; - // deploy with buffer on gas limit - const contract = await contractFactory.deploy(...params, { - gasLimit: addBufferToGasLimit(gasEstimated), - ...overrides, - }); + let contract; + if (technicalStack === ChainTechnicalStack.ZkSync) { + if (!artifact) throw new Error(`No ZkSync contract artifact provided!`); - this.logger.trace( - `Deploying contract ${contract.address} on ${chainNameOrId}:`, - { transaction: deployTx }, - ); + // Handle deployment for ZkSync protocol + const deployer = new ZKSyncDeployer(signer as ZKSyncWallet); - // wait for deploy tx to be confirmed - await this.handleTx(chainNameOrId, contract.deployTransaction); + const estimatedGas = await deployer.estimateDeployGas(artifact, params); + contract = await deployer.deploy(artifact, params, { + gasLimit: addBufferToGasLimit(estimatedGas), + ...overrides, + }); + + this.logger.trace( + `Contract deployed at ${contract.address} on ${chainNameOrId}:`, + ); + } else { + const contractFactory = factory.connect(signer); + const deployTx = contractFactory.getDeployTransaction(...params); + const estimatedGas = await signer.estimateGas(deployTx); + contract = await contractFactory.deploy(...params, { + gasLimit: addBufferToGasLimit(estimatedGas), // 10% buffer + ...overrides, + }); + + // wait for deploy tx to be confirmed + await this.handleTx(chainNameOrId, contract.deployTransaction); + + this.logger.trace( + `Contract deployed at ${contract.address} on ${chainNameOrId}:`, + { transaction: deployTx }, + ); + } // return deployed contract return contract as Awaited>; } @@ -422,7 +461,10 @@ export class MultiProvider extends ChainMetadataManager { * Creates a MultiProvider using the given signer for all test networks */ static createTestMultiProvider( - params: { signer?: Signer; provider?: Provider } = {}, + params: { + signer?: Signer; + provider?: Provider; + } = {}, chains: ChainName[] = testChains, ): MultiProvider { const { signer, provider } = params; diff --git a/typescript/sdk/src/providers/ProviderType.ts b/typescript/sdk/src/providers/ProviderType.ts index ce1740873d..a09f1da325 100644 --- a/typescript/sdk/src/providers/ProviderType.ts +++ b/typescript/sdk/src/providers/ProviderType.ts @@ -21,6 +21,11 @@ import type { Transaction as VTransaction, TransactionReceipt as VTransactionReceipt, } from 'viem'; +import { + Contract as ZKSyncBaseContract, + Provider as ZKSyncBaseProvider, + types as zkSyncTypes, +} from 'zksync-ethers'; import { Annotated, ProtocolType } from '@hyperlane-xyz/utils'; @@ -31,6 +36,7 @@ export enum ProviderType { CosmJs = 'cosmjs', CosmJsWasm = 'cosmjs-wasm', GnosisTxBuilder = 'gnosis-txBuilder', + ZkSync = 'zksync', } export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record< @@ -124,13 +130,19 @@ export interface CosmJsWasmProvider provider: Promise; } +export interface ZKSyncProvider extends TypedProviderBase { + type: ProviderType.ZkSync; + provider: ZKSyncBaseProvider; +} + export type TypedProvider = | EthersV5Provider // | EthersV6Provider | ViemProvider | SolanaWeb3Provider | CosmJsProvider - | CosmJsWasmProvider; + | CosmJsWasmProvider + | ZKSyncProvider; /** * Contracts with discriminated union of provider type @@ -169,13 +181,19 @@ export interface CosmJsWasmContract contract: CosmWasmContract; } +export interface ZKSyncContract extends TypedContractBase { + type: ProviderType.ZkSync; + contract: ZKSyncBaseContract; +} + export type TypedContract = | EthersV5Contract // | EthersV6Contract | ViemContract | SolanaWeb3Contract | CosmJsContract - | CosmJsWasmContract; + | CosmJsWasmContract + | ZKSyncBaseContract; /** * Transactions with discriminated union of provider type @@ -216,13 +234,20 @@ export interface CosmJsWasmTransaction transaction: ExecuteInstruction; } +export interface ZKSyncTransaction + extends TypedTransactionBase { + type: ProviderType.ZkSync; + transaction: zkSyncTypes.TransactionRequest; +} + export type TypedTransaction = | EthersV5Transaction // | EthersV6Transaction | ViemTransaction | SolanaWeb3Transaction | CosmJsTransaction - | CosmJsWasmTransaction; + | CosmJsWasmTransaction + | ZKSyncTransaction; /** * Transaction receipt/response with discriminated union of provider type @@ -263,9 +288,16 @@ export interface CosmJsWasmTransactionReceipt receipt: DeliverTxResponse; } +export interface ZKSyncTransactionReceipt + extends TypedTransactionReceiptBase { + type: ProviderType.ZkSync; + receipt: zkSyncTypes.TransactionReceipt; +} + export type TypedTransactionReceipt = | EthersV5TransactionReceipt | ViemTransactionReceipt | SolanaWeb3TransactionReceipt | CosmJsTransactionReceipt - | CosmJsWasmTransactionReceipt; + | CosmJsWasmTransactionReceipt + | ZKSyncTransactionReceipt; diff --git a/typescript/sdk/src/providers/providerBuilders.ts b/typescript/sdk/src/providers/providerBuilders.ts index bc8051b1ba..cfe773de95 100644 --- a/typescript/sdk/src/providers/providerBuilders.ts +++ b/typescript/sdk/src/providers/providerBuilders.ts @@ -3,8 +3,9 @@ import { StargateClient } from '@cosmjs/stargate'; import { Connection } from '@solana/web3.js'; import { providers } from 'ethers'; import { createPublicClient, http } from 'viem'; +import { Provider as ZKProvider } from 'zksync-ethers'; -import { ProtocolType, isNumeric } from '@hyperlane-xyz/utils'; +import { ProtocolType, assert, isNumeric } from '@hyperlane-xyz/utils'; import { ChainMetadata, RpcUrl } from '../metadata/chainMetadataTypes.js'; @@ -16,6 +17,7 @@ import { SolanaWeb3Provider, TypedProvider, ViemProvider, + ZKSyncProvider, } from './ProviderType.js'; import { HyperlaneSmartProvider } from './SmartProvider/SmartProvider.js'; import { ProviderRetryOptions } from './SmartProvider/types.js'; @@ -109,6 +111,16 @@ export function defaultCosmJsWasmProviderBuilder( }; } +export function defaultZKSyncProviderBuilder( + rpcUrls: RpcUrl[], + network: providers.Networkish, +): ZKSyncProvider { + assert(rpcUrls.length, 'No RPC URLs provided'); + const url = rpcUrls[0].http; + const provider = new ZKProvider(url, network); + return { type: ProviderType.ZkSync, provider }; +} + // Kept for backwards compatibility export function defaultProviderBuilder( rpcUrls: RpcUrl[], @@ -117,6 +129,13 @@ export function defaultProviderBuilder( return defaultEthersV5ProviderBuilder(rpcUrls, _network).provider; } +export function defaultZKProviderBuilder( + rpcUrls: RpcUrl[], + _network: number | string, +): ZKProvider { + return defaultZKSyncProviderBuilder(rpcUrls, _network).provider; +} + export type ProviderBuilderMap = Record< ProviderType, ProviderBuilderFn @@ -128,6 +147,7 @@ export const defaultProviderBuilderMap: ProviderBuilderMap = { [ProviderType.SolanaWeb3]: defaultSolProviderBuilder, [ProviderType.CosmJs]: defaultCosmJsProviderBuilder, [ProviderType.CosmJsWasm]: defaultCosmJsWasmProviderBuilder, + [ProviderType.ZkSync]: defaultZKSyncProviderBuilder, }; export const protocolToDefaultProviderBuilder: Record< diff --git a/typescript/sdk/src/utils/zksync.ts b/typescript/sdk/src/utils/zksync.ts new file mode 100644 index 0000000000..0b97e4330c --- /dev/null +++ b/typescript/sdk/src/utils/zksync.ts @@ -0,0 +1,32 @@ +import { ZKSyncArtifact, loadAllZKSyncArtifacts } from '@hyperlane-xyz/core'; + +/** + * @dev Retrieves a ZkSync artifact by its contract name or qualified name. + * @param name The name of the contract or qualified name in the format "sourceName:contractName". + * @return The corresponding ZKSyncArtifact if found, or undefined if not found. + */ +export const getZKSyncArtifactByContractName = async ( + name: string, +): Promise => { + // Load all ZkSync artifacts + const allArtifacts = loadAllZKSyncArtifacts(); + + // Find the artifact that matches the contract name or qualified name + const artifact = Object.values(allArtifacts).find( + ({ contractName, sourceName }: ZKSyncArtifact) => { + const lowerCaseContractName = contractName.toLowerCase(); + const lowerCaseName = name.toLowerCase(); + + // Check if the contract name matches + if (lowerCaseContractName === lowerCaseName) { + return true; + } + + // Check if the qualified name matches + const qualifiedName = `${sourceName}:${contractName}`; + return qualifiedName === name; // Return true if qualified name matches + }, + ); + + return artifact; +}; diff --git a/typescript/sdk/src/zksync/ZKSyncDeployer.ts b/typescript/sdk/src/zksync/ZKSyncDeployer.ts new file mode 100644 index 0000000000..9206926311 --- /dev/null +++ b/typescript/sdk/src/zksync/ZKSyncDeployer.ts @@ -0,0 +1,185 @@ +import { BigNumber, BytesLike, Overrides, utils } from 'ethers'; +import { + Contract, + ContractFactory, + Wallet, + types as zksyncTypes, +} from 'zksync-ethers'; + +import { ZKSyncArtifact } from '@hyperlane-xyz/core'; +import { assert } from '@hyperlane-xyz/utils'; + +import { defaultZKProviderBuilder } from '../providers/providerBuilders.js'; +import { getZKSyncArtifactByContractName } from '../utils/zksync.js'; + +/** + * Class for deploying contracts to the ZKSync network. + */ +export class ZKSyncDeployer { + public zkWallet: Wallet; + public deploymentType?: zksyncTypes.DeploymentType; + + constructor(zkWallet: Wallet, deploymentType?: zksyncTypes.DeploymentType) { + this.deploymentType = deploymentType; + + this.zkWallet = zkWallet.connect( + zkWallet.provider ?? + defaultZKProviderBuilder([{ http: 'http://127.0.0.1:8011' }], 260), + ); + } + + /** + * Loads and validates a ZKSync contract artifact by name + * @param contractTitle - Contract name or qualified name (sourceName:contractName) + * + * @returns The ZKSync artifact + */ + private async loadArtifact(contractTitle: string): Promise { + const artifact = await getZKSyncArtifactByContractName(contractTitle); + assert(artifact, `No ZKSync artifact for contract ${contractTitle} found!`); + return artifact; + } + + /** + * Estimates the price of calling a deploy transaction in ETH. + * + * @param artifact The previously loaded artifact object. + * @param constructorArguments List of arguments to be passed to the contract constructor. + * + * @returns Calculated fee in ETH wei + */ + public async estimateDeployFee( + artifact: ZKSyncArtifact, + constructorArguments: any[], + ): Promise { + const gas = await this.estimateDeployGas(artifact, constructorArguments); + const gasPrice = await this.zkWallet.provider.getGasPrice(); + return gas.mul(gasPrice); + } + + /** + * Estimates the amount of gas needed to execute a deploy transaction. + * + * @param artifact The previously loaded artifact object. + * @param constructorArguments List of arguments to be passed to the contract constructor. + * + * @returns Calculated amount of gas. + */ + public async estimateDeployGas( + artifact: ZKSyncArtifact, + constructorArguments: any[], + ): Promise { + const factoryDeps = await this.extractFactoryDeps(artifact); + + const factory = new ContractFactory( + artifact.abi, + artifact.bytecode, + this.zkWallet, + this.deploymentType, + ); + + // Encode deploy transaction so it can be estimated. + const deployTx = factory.getDeployTransaction(...constructorArguments, { + customData: { + factoryDeps, + }, + }); + deployTx.from = this.zkWallet.address; + + return this.zkWallet.provider.estimateGas(deployTx); + } + + /** + * Sends a deploy transaction to the zkSync network. + * For now, it will use defaults for the transaction parameters: + * - fee amount is requested automatically from the zkSync server. + * + * @param artifact The previously loaded artifact object. + * @param constructorArguments List of arguments to be passed to the contract constructor. + * @param overrides Optional object with additional deploy transaction parameters. + * @param additionalFactoryDeps Additional contract bytecodes to be added to the factory dependencies list. + * + * @returns A contract object. + */ + public async deploy( + artifact: ZKSyncArtifact, + constructorArguments: any[] = [], + overrides?: Overrides, + additionalFactoryDeps?: BytesLike[], + ): Promise { + const baseDeps = await this.extractFactoryDeps(artifact); + const additionalDeps = additionalFactoryDeps + ? additionalFactoryDeps.map((val) => utils.hexlify(val)) + : []; + const factoryDeps = [...baseDeps, ...additionalDeps]; + + const factory = new ContractFactory( + artifact.abi, + artifact.bytecode, + this.zkWallet, + this.deploymentType, + ); + + const { customData, ..._overrides } = overrides ?? {}; + + // Encode and send the deploy transaction providing factory dependencies. + const contract = await factory.deploy(...constructorArguments, { + ..._overrides, + customData: { + ...customData, + factoryDeps, + }, + }); + + await contract.deployed(); + + return contract; + } + + /** + * Extracts factory dependencies from the artifact. + * + * @param artifact Artifact to extract dependencies from + * + * @returns Factory dependencies in the format expected by SDK. + */ + async extractFactoryDeps(artifact: ZKSyncArtifact): Promise { + const visited = new Set(); + + visited.add(`${artifact.sourceName}:${artifact.contractName}`); + return this.extractFactoryDepsRecursive(artifact, visited); + } + + private async extractFactoryDepsRecursive( + artifact: ZKSyncArtifact, + visited: Set, + ): Promise { + // Load all the dependency bytecodes. + // We transform it into an array of bytecodes. + const factoryDeps: string[] = []; + for (const dependencyHash in artifact.factoryDeps) { + if ( + Object.prototype.hasOwnProperty.call( + artifact.factoryDeps, + dependencyHash, + ) + ) { + const dependencyContract = artifact.factoryDeps[dependencyHash]; + if (!visited.has(dependencyContract)) { + const dependencyArtifact = await this.loadArtifact( + dependencyContract, + ); + factoryDeps.push(dependencyArtifact.bytecode); + visited.add(dependencyContract); + const transitiveDeps = await this.extractFactoryDepsRecursive( + dependencyArtifact, + visited, + ); + factoryDeps.push(...transitiveDeps); + } + } + } + + return factoryDeps; + } +} diff --git a/yarn.lock b/yarn.lock index 75ff7cba40..e02599c648 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4240,6 +4240,13 @@ __metadata: languageName: node linkType: hard +"@balena/dockerignore@npm:^1.0.2": + version: 1.0.2 + resolution: "@balena/dockerignore@npm:1.0.2" + checksum: 10/13d654fdd725008577d32e721c720275bdc48f72bce612326363d5bed449febbed856c517a0b23c7c40d87cb531e63432804550b4ecc13e365d26fee38fb6c8a + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -7214,6 +7221,16 @@ __metadata: languageName: node linkType: hard +"@grpc/grpc-js@npm:^1.11.1": + version: 1.12.5 + resolution: "@grpc/grpc-js@npm:1.12.5" + dependencies: + "@grpc/proto-loader": "npm:^0.7.13" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10/4f8ead236dcab4d94e15e62d65ad2d93732d37f5cc52ffafe67ae00f69eae4a4c97d6d34a1b9eac9f30206468f2d15302ea6649afcba1d38929afa9d1e7c12d5 + languageName: node + linkType: hard + "@grpc/grpc-js@npm:~1.10.3": version: 1.10.8 resolution: "@grpc/grpc-js@npm:1.10.8" @@ -7370,6 +7387,7 @@ __metadata: "@hyperlane-xyz/utils": "npm:8.5.0" "@layerzerolabs/lz-evm-oapp-v2": "npm:2.0.2" "@layerzerolabs/solidity-examples": "npm:^1.1.0" + "@matterlabs/hardhat-zksync-solc": "npm:^1.2.4" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts": "npm:^4.9.3" @@ -7396,6 +7414,7 @@ __metadata: tsx: "npm:^4.19.1" typechain: "patch:typechain@npm%3A8.3.2#~/.yarn/patches/typechain-npm-8.3.2-b02e27439e.patch" typescript: "npm:5.3.3" + zksync-ethers: "npm:^5.10.0" peerDependencies: "@ethersproject/abi": "*" "@ethersproject/providers": "*" @@ -7594,6 +7613,7 @@ __metadata: typescript: "npm:5.3.3" viem: "npm:^2.21.45" yaml: "npm:2.4.5" + zksync-ethers: "npm:^5.10.0" zod: "npm:^3.21.2" peerDependencies: "@ethersproject/abi": "*" @@ -8742,6 +8762,27 @@ __metadata: languageName: node linkType: hard +"@matterlabs/hardhat-zksync-solc@npm:^1.2.4": + version: 1.2.5 + resolution: "@matterlabs/hardhat-zksync-solc@npm:1.2.5" + dependencies: + "@nomiclabs/hardhat-docker": "npm:^2.0.2" + chai: "npm:^4.3.4" + chalk: "npm:^4.1.2" + debug: "npm:^4.3.5" + dockerode: "npm:^4.0.2" + fs-extra: "npm:^11.2.0" + proper-lockfile: "npm:^4.1.2" + semver: "npm:^7.6.2" + sinon: "npm:^18.0.0" + sinon-chai: "npm:^3.7.0" + undici: "npm:^6.18.2" + peerDependencies: + hardhat: ^2.22.5 + checksum: 10/0452ad5504258fad2f2d10be40cc79bb0e65d3470af75f70f60e4a94bb92f238031a13587cda51a0dfd77ef1cb028145088e2628a02db6d7a5ac721bf2816bf0 + languageName: node + linkType: hard + "@mdx-js/react@npm:^2.1.5": version: 2.3.0 resolution: "@mdx-js/react@npm:2.3.0" @@ -9627,6 +9668,17 @@ __metadata: languageName: node linkType: hard +"@nomiclabs/hardhat-docker@npm:^2.0.2": + version: 2.0.2 + resolution: "@nomiclabs/hardhat-docker@npm:2.0.2" + dependencies: + dockerode: "npm:^2.5.8" + fs-extra: "npm:^7.0.1" + node-fetch: "npm:^2.6.0" + checksum: 10/2b6601a7bcac115a24dc4d2ce35b76b1748ffaebd723afad17e8f506231e1d6c7e5c9df73b29d429c5eb01cb0e11ff92f10c746ca31343b0fd3ddc449c9ec8f3 + languageName: node + linkType: hard + "@nomiclabs/hardhat-ethers@npm:^2.2.3": version: 2.2.3 resolution: "@nomiclabs/hardhat-ethers@npm:2.2.3" @@ -12975,7 +13027,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^3.0.0": +"@sinonjs/commons@npm:^3.0.0, @sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" dependencies: @@ -12984,6 +13036,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:11.2.2": + version: 11.2.2 + resolution: "@sinonjs/fake-timers@npm:11.2.2" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10/da7dfa677b2362bc5a321fc1563184755b5c62fbb1a72457fb9e901cd187ba9dc834f9e8a0fb5a4e1d1e6e6ad4c5b54e90900faa44dd6c82d3c49c92ec23ecd4 + languageName: node + linkType: hard + "@sinonjs/fake-timers@npm:>=5, @sinonjs/fake-timers@npm:^9.1.2": version: 9.1.2 resolution: "@sinonjs/fake-timers@npm:9.1.2" @@ -13002,6 +13063,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^13.0.1": + version: 13.0.5 + resolution: "@sinonjs/fake-timers@npm:13.0.5" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f + languageName: node + linkType: hard + "@sinonjs/samsam@npm:^6.1.1": version: 6.1.1 resolution: "@sinonjs/samsam@npm:6.1.1" @@ -13013,6 +13083,17 @@ __metadata: languageName: node linkType: hard +"@sinonjs/samsam@npm:^8.0.0": + version: 8.0.2 + resolution: "@sinonjs/samsam@npm:8.0.2" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + lodash.get: "npm:^4.4.2" + type-detect: "npm:^4.1.0" + checksum: 10/58ca9752e8e835a09ed275f8edf8da2720fe95c0c02f6bcb90ad7f86fdceb393f35f744194b705dd94216228646ec0aedbb814e245eb869b940dcf1266b7a533 + languageName: node + linkType: hard + "@sinonjs/text-encoding@npm:^0.7.1": version: 0.7.1 resolution: "@sinonjs/text-encoding@npm:0.7.1" @@ -13020,6 +13101,13 @@ __metadata: languageName: node linkType: hard +"@sinonjs/text-encoding@npm:^0.7.3": + version: 0.7.3 + resolution: "@sinonjs/text-encoding@npm:0.7.3" + checksum: 10/f0cc89bae36e7ce159187dece7800b78831288f1913e9ae8cf8a878da5388232d2049740f6f4a43ec4b43b8ad1beb55f919f45eb9a577adb4a2a6eacb27b25fc + languageName: node + linkType: hard + "@smithy/abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "@smithy/abort-controller@npm:3.0.0" @@ -17079,6 +17167,18 @@ __metadata: languageName: node linkType: hard +"JSONStream@npm:1.3.2": + version: 1.3.2 + resolution: "JSONStream@npm:1.3.2" + dependencies: + jsonparse: "npm:^1.2.0" + through: "npm:>=2.2.7 <3" + bin: + JSONStream: ./bin.js + checksum: 10/3a1274f39e9b0369da5d5536906b527113326434a43b92923ac2d3c2d449009253b245055de2633b1d9ca7ae30054b6091d755e79f0cb1c7dab9b6b253871812 + languageName: node + linkType: hard + "JSONStream@npm:^1.3.5": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -17903,7 +18003,7 @@ __metadata: languageName: node linkType: hard -"asn1@npm:~0.2.3": +"asn1@npm:^0.2.6, asn1@npm:~0.2.3": version: 0.2.6 resolution: "asn1@npm:0.2.6" dependencies: @@ -18317,7 +18417,7 @@ __metadata: languageName: node linkType: hard -"bcrypt-pbkdf@npm:^1.0.0": +"bcrypt-pbkdf@npm:^1.0.0, bcrypt-pbkdf@npm:^1.0.2": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" dependencies: @@ -18485,6 +18585,16 @@ __metadata: languageName: node linkType: hard +"bl@npm:^1.0.0": + version: 1.2.3 + resolution: "bl@npm:1.2.3" + dependencies: + readable-stream: "npm:^2.3.5" + safe-buffer: "npm:^5.1.1" + checksum: 10/11d775b09ebd7d8c0df1ed7efd03cc8a2b1283c804a55153c81a0b586728a085fa24240647cac9a60163eb6f36a28cf8c45b80bf460a46336d4c84c40205faff + languageName: node + linkType: hard + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -18809,6 +18919,23 @@ __metadata: languageName: node linkType: hard +"buffer-alloc-unsafe@npm:^1.1.0": + version: 1.1.0 + resolution: "buffer-alloc-unsafe@npm:1.1.0" + checksum: 10/c5e18bf51f67754ec843c9af3d4c005051aac5008a3992938dda1344e5cfec77c4b02b4ca303644d1e9a6e281765155ce6356d85c6f5ccc5cd21afc868def396 + languageName: node + linkType: hard + +"buffer-alloc@npm:^1.2.0": + version: 1.2.0 + resolution: "buffer-alloc@npm:1.2.0" + dependencies: + buffer-alloc-unsafe: "npm:^1.1.0" + buffer-fill: "npm:^1.0.0" + checksum: 10/560cd27f3cbe73c614867da373407d4506309c62fe18de45a1ce191f3785ec6ca2488d802ff82065798542422980ca25f903db078c57822218182c37c3576df5 + languageName: node + linkType: hard + "buffer-crc32@npm:~0.2.3": version: 0.2.13 resolution: "buffer-crc32@npm:0.2.13" @@ -18823,6 +18950,13 @@ __metadata: languageName: node linkType: hard +"buffer-fill@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-fill@npm:1.0.0" + checksum: 10/c29b4723ddeab01e74b5d3b982a0c6828f2ded49cef049ddca3dac661c874ecdbcecb5dd8380cf0f4adbeb8cff90a7de724126750a1f1e5ebd4eb6c59a1315b1 + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -18928,6 +19062,13 @@ __metadata: languageName: node linkType: hard +"buildcheck@npm:~0.0.6": + version: 0.0.6 + resolution: "buildcheck@npm:0.0.6" + checksum: 10/194ee8d3b0926fd6f3e799732130ad7ab194882c56900b8670ad43c81326f64871f49b7d9f1e9baad91ca3070eb4e8b678797fe9ae78cf87dde86d8916eb25d2 + languageName: node + linkType: hard + "busboy@npm:^1.6.0": version: 1.6.0 resolution: "busboy@npm:1.6.0" @@ -19386,7 +19527,7 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^1.1.1, chownr@npm:^1.1.4": +"chownr@npm:^1.0.1, chownr@npm:^1.1.1, chownr@npm:^1.1.4": version: 1.1.4 resolution: "chownr@npm:1.1.4" checksum: 10/115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d @@ -19921,7 +20062,7 @@ __metadata: languageName: node linkType: hard -"concat-stream@npm:^1.6.0, concat-stream@npm:^1.6.2": +"concat-stream@npm:^1.6.0, concat-stream@npm:^1.6.2, concat-stream@npm:~1.6.2": version: 1.6.2 resolution: "concat-stream@npm:1.6.2" dependencies: @@ -20150,6 +20291,17 @@ __metadata: languageName: node linkType: hard +"cpu-features@npm:~0.0.10": + version: 0.0.10 + resolution: "cpu-features@npm:0.0.10" + dependencies: + buildcheck: "npm:~0.0.6" + nan: "npm:^2.19.0" + node-gyp: "npm:latest" + checksum: 10/941b828ffe77582b2bdc03e894c913e2e2eeb5c6043ccb01338c34446d026f6888dc480ecb85e684809f9c3889d245f3648c7907eb61a92bdfc6aed039fcda8d + languageName: node + linkType: hard + "crc-32@npm:^1.2.0": version: 1.2.2 resolution: "crc-32@npm:1.2.2" @@ -20479,7 +20631,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.1.0, debug@npm:^3.2.7": +"debug@npm:^3.1.0, debug@npm:^3.2.6, debug@npm:^3.2.7": version: 3.2.7 resolution: "debug@npm:3.2.7" dependencies: @@ -20941,6 +21093,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.2.0": + version: 5.2.0 + resolution: "diff@npm:5.2.0" + checksum: 10/01b7b440f83a997350a988e9d2f558366c0f90f15be19f4aa7f1bb3109a4e153dfc3b9fbf78e14ea725717017407eeaa2271e3896374a0181e8f52445740846d + languageName: node + linkType: hard + "difflib@npm:^0.2.4": version: 0.2.4 resolution: "difflib@npm:0.2.4" @@ -20973,6 +21132,56 @@ __metadata: languageName: node linkType: hard +"docker-modem@npm:^1.0.8": + version: 1.0.9 + resolution: "docker-modem@npm:1.0.9" + dependencies: + JSONStream: "npm:1.3.2" + debug: "npm:^3.2.6" + readable-stream: "npm:~1.0.26-4" + split-ca: "npm:^1.0.0" + checksum: 10/2ade3d9f1b25231a5ecadcbfb9401a397eff3de2eec7add8130de1c40004faaa58fe074e5110ccef12957973089e5911b711648c77944a4a15d908e9b9605549 + languageName: node + linkType: hard + +"docker-modem@npm:^5.0.6": + version: 5.0.6 + resolution: "docker-modem@npm:5.0.6" + dependencies: + debug: "npm:^4.1.1" + readable-stream: "npm:^3.5.0" + split-ca: "npm:^1.0.1" + ssh2: "npm:^1.15.0" + checksum: 10/4977797814c29205f0762215f2e3e26600986bb65139018ff6840ff4c596e5d19f3002be1abcc5e73e3828870bb73bab28275a6458ad027ed56ab61fca014b6d + languageName: node + linkType: hard + +"dockerode@npm:^2.5.8": + version: 2.5.8 + resolution: "dockerode@npm:2.5.8" + dependencies: + concat-stream: "npm:~1.6.2" + docker-modem: "npm:^1.0.8" + tar-fs: "npm:~1.16.3" + checksum: 10/13111cfcaf47905cd2cd323a07cb5b79404ef5e9032e33ef3a6f71d1f72283d9b2921b6de955c8454b147bbf4db33822a80d960b2250e3e8aed62ffe0b43083f + languageName: node + linkType: hard + +"dockerode@npm:^4.0.2": + version: 4.0.4 + resolution: "dockerode@npm:4.0.4" + dependencies: + "@balena/dockerignore": "npm:^1.0.2" + "@grpc/grpc-js": "npm:^1.11.1" + "@grpc/proto-loader": "npm:^0.7.13" + docker-modem: "npm:^5.0.6" + protobufjs: "npm:^7.3.2" + tar-fs: "npm:~2.0.1" + uuid: "npm:^10.0.0" + checksum: 10/db2304e6125d0c4246833eaa6a389497c98564ba2ed18fa465eace1b6eb6c2a41f1711fc1e57cd2fc0f7ca6be80eeca43b0c35cd6b86205e34faf9acb0f72bcc + languageName: node + linkType: hard + "doctrine@npm:^2.1.0": version: 2.1.0 resolution: "doctrine@npm:2.1.0" @@ -23672,6 +23881,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.2.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/c9fe7b23dded1efe7bbae528d685c3206477e20cc60e9aaceb3f024f9b9ff2ee1f62413c161cb88546cc564009ab516dec99e9781ba782d869bb37e4fe04a97f + languageName: node + linkType: hard + "fs-extra@npm:^4.0.2": version: 4.0.3 resolution: "fs-extra@npm:4.0.3" @@ -27284,6 +27504,13 @@ __metadata: languageName: node linkType: hard +"just-extend@npm:^6.2.0": + version: 6.2.0 + resolution: "just-extend@npm:6.2.0" + checksum: 10/1f487b074b9e5773befdd44dc5d1b446f01f24f7d4f1f255d51c0ef7f686e8eb5f95d983b792b9ca5c8b10cd7e60a924d64103725759eddbd7f18bcb22743f92 + languageName: node + linkType: hard + "jwa@npm:^2.0.0": version: 2.0.0 resolution: "jwa@npm:2.0.0" @@ -29112,7 +29339,7 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.13.2": +"nan@npm:^2.13.2, nan@npm:^2.19.0, nan@npm:^2.20.0": version: 2.22.0 resolution: "nan@npm:2.22.0" dependencies: @@ -29224,6 +29451,19 @@ __metadata: languageName: node linkType: hard +"nise@npm:^6.0.0": + version: 6.1.1 + resolution: "nise@npm:6.1.1" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:^13.0.1" + "@sinonjs/text-encoding": "npm:^0.7.3" + just-extend: "npm:^6.2.0" + path-to-regexp: "npm:^8.1.0" + checksum: 10/2d3175587cf0a351e2c91eb643fdc59d266de39f394a3ac0bace38571749d1e7f25341d763899245139b8f0d2ee048b2d3387d75ecf94c4897e947d5fc881eea + languageName: node + linkType: hard + "nock@npm:13.5.4": version: 13.5.4 resolution: "nock@npm:13.5.4" @@ -30362,6 +30602,13 @@ __metadata: languageName: node linkType: hard +"path-to-regexp@npm:^8.1.0": + version: 8.2.0 + resolution: "path-to-regexp@npm:8.2.0" + checksum: 10/23378276a172b8ba5f5fb824475d1818ca5ccee7bbdb4674701616470f23a14e536c1db11da9c9e6d82b82c556a817bbf4eee6e41b9ed20090ef9427cbb38e13 + languageName: node + linkType: hard + "path-type@npm:^4.0.0": version: 4.0.0 resolution: "path-type@npm:4.0.0" @@ -31016,7 +31263,7 @@ __metadata: languageName: node linkType: hard -"proper-lockfile@npm:^4.1.1": +"proper-lockfile@npm:^4.1.1, proper-lockfile@npm:^4.1.2": version: 4.1.2 resolution: "proper-lockfile@npm:4.1.2" dependencies: @@ -31107,6 +31354,26 @@ __metadata: languageName: node linkType: hard +"protobufjs@npm:^7.3.2": + version: 7.4.0 + resolution: "protobufjs@npm:7.4.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: 10/408423506610f70858d7593632f4a6aa4f05796c90fd632be9b9252457c795acc71aa6d3b54bb7f48a890141728fee4ca3906723ccea6c202ad71f21b3879b8b + languageName: node + linkType: hard + "proxy-addr@npm:~2.0.7": version: 2.0.7 resolution: "proxy-addr@npm:2.0.7" @@ -31159,6 +31426,16 @@ __metadata: languageName: node linkType: hard +"pump@npm:^1.0.0": + version: 1.0.3 + resolution: "pump@npm:1.0.3" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10/61fe58694f9900020a5cf5bc765d74396891c201afecf06659df2f5874fd832be4e19e2f95cc72d8b9eb98ace0a4db3cebf7343f9fc893a930577be29e3ad8b5 + languageName: node + linkType: hard + "pump@npm:^2.0.0": version: 2.0.1 resolution: "pump@npm:2.0.1" @@ -31889,7 +32166,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.0, readable-stream@npm:^2.3.3, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.3, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -31919,7 +32196,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.1.0, readable-stream@npm:^3.6.2": +"readable-stream@npm:^3.1.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.2": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -31954,6 +32231,18 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:~1.0.26-4": + version: 1.0.34 + resolution: "readable-stream@npm:1.0.34" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.1" + isarray: "npm:0.0.1" + string_decoder: "npm:~0.10.x" + checksum: 10/20537fca5a8ffd4af0f483be1cce0e981ed8cbb1087e0c762e2e92ae77f1005627272cebed8422f28047b465056aa1961fefd24baf532ca6a3616afea6811ae0 + languageName: node + linkType: hard + "readdirp@npm:~3.2.0": version: 3.2.0 resolution: "readdirp@npm:3.2.0" @@ -33398,6 +33687,16 @@ __metadata: languageName: node linkType: hard +"sinon-chai@npm:^3.7.0": + version: 3.7.0 + resolution: "sinon-chai@npm:3.7.0" + peerDependencies: + chai: ^4.0.0 + sinon: ">=4.0.0" + checksum: 10/028853eb8a545ca613c6863014a40f07d1e6b81467e20939fefcd13f170206d24165b91099fb297aeb4d137745e321da25daa8e2d665cc0a78f90d5b877e8bbe + languageName: node + linkType: hard + "sinon@npm:^13.0.2": version: 13.0.2 resolution: "sinon@npm:13.0.2" @@ -33412,6 +33711,20 @@ __metadata: languageName: node linkType: hard +"sinon@npm:^18.0.0": + version: 18.0.1 + resolution: "sinon@npm:18.0.1" + dependencies: + "@sinonjs/commons": "npm:^3.0.1" + "@sinonjs/fake-timers": "npm:11.2.2" + "@sinonjs/samsam": "npm:^8.0.0" + diff: "npm:^5.2.0" + nise: "npm:^6.0.0" + supports-color: "npm:^7" + checksum: 10/65be65a7c5dbef7e9e315820bcd17fe0387fb07cb5dd41d3d6a89177e9a2ca463144676d8407348dc075758daa7cf37b9376d35bd1bc7e06717e87d03aa26111 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -33911,6 +34224,13 @@ __metadata: languageName: node linkType: hard +"split-ca@npm:^1.0.0, split-ca@npm:^1.0.1": + version: 1.0.1 + resolution: "split-ca@npm:1.0.1" + checksum: 10/1e7409938a95ee843fe2593156a5735e6ee63772748ee448ea8477a5a3e3abde193c3325b3696e56a5aff07c7dcf6b1f6a2f2a036895b4f3afe96abb366d893f + languageName: node + linkType: hard + "split-on-first@npm:^1.0.0": version: 1.1.0 resolution: "split-on-first@npm:1.1.0" @@ -33932,6 +34252,23 @@ __metadata: languageName: node linkType: hard +"ssh2@npm:^1.15.0": + version: 1.16.0 + resolution: "ssh2@npm:1.16.0" + dependencies: + asn1: "npm:^0.2.6" + bcrypt-pbkdf: "npm:^1.0.2" + cpu-features: "npm:~0.0.10" + nan: "npm:^2.20.0" + dependenciesMeta: + cpu-features: + optional: true + nan: + optional: true + checksum: 10/0951c22d9c5a0e3b89a8e5ae890ebcbce9f1f94dbed37d1490e4e48e26bc8b074fa81f202ee57b708e31b5f33033f4c870b92047f4f02b6bc26c32225b01d84c + languageName: node + linkType: hard + "sshpk@npm:^1.7.0": version: 1.17.0 resolution: "sshpk@npm:1.17.0" @@ -34330,6 +34667,13 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~0.10.x": + version: 0.10.31 + resolution: "string_decoder@npm:0.10.31" + checksum: 10/cc43e6b1340d4c7843da0e37d4c87a4084c2342fc99dcf6563c3ec273bb082f0cbd4ebf25d5da19b04fb16400d393885fda830be5128e1c416c73b5a6165f175 + languageName: node + linkType: hard + "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -34567,7 +34911,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": +"supports-color@npm:^7, supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -34794,7 +35138,46 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.1.4": +"tar-fs@npm:~1.16.3": + version: 1.16.4 + resolution: "tar-fs@npm:1.16.4" + dependencies: + chownr: "npm:^1.0.1" + mkdirp: "npm:^0.5.1" + pump: "npm:^1.0.0" + tar-stream: "npm:^1.1.2" + checksum: 10/2392fb92ebcdbf1b70961192a0f32955dbfc5cd844d4980e7e0dc7b8cb269d6e23831ae8ceb8137322367dcc42ae946f2349d0fd79b89b3c30b0a7d3ff5fc15e + languageName: node + linkType: hard + +"tar-fs@npm:~2.0.1": + version: 2.0.1 + resolution: "tar-fs@npm:2.0.1" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.0.0" + checksum: 10/85ceac6fce0e9175b5b67c0eca8864b7d29a940cae8b7657c60b66e8a252319d701c3df12814162a6839e6120f9e1975757293bdeaf294ad5b15721d236c4d32 + languageName: node + linkType: hard + +"tar-stream@npm:^1.1.2": + version: 1.6.2 + resolution: "tar-stream@npm:1.6.2" + dependencies: + bl: "npm:^1.0.0" + buffer-alloc: "npm:^1.2.0" + end-of-stream: "npm:^1.0.0" + fs-constants: "npm:^1.0.0" + readable-stream: "npm:^2.3.0" + to-buffer: "npm:^1.1.1" + xtend: "npm:^4.0.0" + checksum: 10/ac9b850bd40e6d4b251abcf92613bafd9fc9e592c220c781ebcdbb0ba76da22a245d9ea3ea638ad7168910e7e1ae5079333866cd679d2f1ffadb99c403f99d7f + languageName: node + linkType: hard + +"tar-stream@npm:^2.0.0, tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -35096,6 +35479,13 @@ __metadata: languageName: node linkType: hard +"to-buffer@npm:^1.1.1": + version: 1.1.1 + resolution: "to-buffer@npm:1.1.1" + checksum: 10/8ade59fe04239b281496b6067bc83ad0371a3657552276cbd09ffffaeb3ad0018a28306d61b854b83280eabe1829cbc53001ccd761e834c6062cbcc7fee2766a + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -36358,6 +36748,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10/35aa60614811a201ff90f8ca5e9ecb7076a75c3821e17f0f5ff72d44e36c2d35fcbc2ceee9c4ac7317f4cc41895da30e74f3885e30313bee48fda6338f250538 + languageName: node + linkType: hard + "uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0"