diff --git a/.github/release-please/config.json b/.github/release-please/config.json index 6242f3e4f..34a8e4f13 100644 --- a/.github/release-please/config.json +++ b/.github/release-please/config.json @@ -13,6 +13,10 @@ "release-type": "node", "component": "@matterlabs/hardhat-zksync-upgradable" }, + "packages/hardhat-zksync-ethers": { + "release-type": "node", + "component": "@matterlabs/hardhat-zksync-ethers" + }, "packages/hardhat-zksync": { "release-type": "node", "component": "@matterlabs/hardhat-zksync" diff --git a/.github/release-please/manifest.json b/.github/release-please/manifest.json index 060853042..cc327fbfe 100644 --- a/.github/release-please/manifest.json +++ b/.github/release-please/manifest.json @@ -1,5 +1,6 @@ { "packages/hardhat-zksync-deploy": "0.11.0", "packages/hardhat-zksync-upgradable": "0.5.2", + "packages/hardhat-zksync-ethers": "0.1.0", "packages/hardhat-zksync": "0.2.0" } \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df2983e2a..41974ace2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,10 +64,18 @@ jobs: pnpm hardhat run scripts/upgrade-factory-beacon.ts pnpm hardhat run scripts/upgrade-factory-uups.ts pnpm hardhat run scripts/upgrade-factory.ts + + - name: Test zksync-ethers example + run: | + cd examples/zksync-ethers-example + pnpm hardhat compile + pnpm hardhat deploy-zksync + - name: Show logs if: always() run: | cat server.log + upgradable: runs-on: ubuntu-latest @@ -143,4 +151,37 @@ jobs: - name: Show logs if: always() run: | - cat server.log \ No newline at end of file + cat server.log + zksync-ethers: + runs-on: ubuntu-latest + name: zksync-ethers + steps: + - uses: actions/checkout@v3 + + - uses: actions/checkout@v3 + with: + repository: matter-labs/local-setup + path: local-setup + + - name: Run server + run: | + cd local-setup + ./start.sh &>../server.log & + - uses: pnpm/action-setup@v3 + + - uses: actions/setup-node@v3 + with: + node-version: "18" + cache: pnpm + + - name: Setup environment + run: | + pnpm install + pnpm build + - name: Wait until server is up + run: | + while ! curl -s -X POST -d '{"jsonrpc":"2.0","method":"net_version","id":1}' -H 'Content-Type: application/json' 0.0.0.0:3050; do sleep 1; done + - name: Test zksync ethers plugin + run: | + cd packages/hardhat-zksync-ethers + pnpm test \ No newline at end of file diff --git a/.github/workflows/npm-publish.yaml b/.github/workflows/npm-publish.yaml index 1d1905871..76d07a50a 100644 --- a/.github/workflows/npm-publish.yaml +++ b/.github/workflows/npm-publish.yaml @@ -22,6 +22,7 @@ on: options: - hardhat-zksync-deploy - hardhat-zksync-upgradable + - hardhat-zksync-ethers - hardhat-zksync jobs: diff --git a/examples/zksync-ethers-example/.eslintrc.js b/examples/zksync-ethers-example/.eslintrc.js new file mode 100644 index 000000000..6437502ed --- /dev/null +++ b/examples/zksync-ethers-example/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: [`${__dirname}/../../config/eslint/eslintrc.cjs`], + parserOptions: { + project: `${__dirname}/tsconfig.json`, + sourceType: "module", + }, +}; \ No newline at end of file diff --git a/examples/zksync-ethers-example/.gitignore b/examples/zksync-ethers-example/.gitignore new file mode 100644 index 000000000..183b3751a --- /dev/null +++ b/examples/zksync-ethers-example/.gitignore @@ -0,0 +1,3 @@ +cache +artifacts +contracts/tmp diff --git a/examples/zksync-ethers-example/README.md b/examples/zksync-ethers-example/README.md new file mode 100644 index 000000000..1edd867d6 --- /dev/null +++ b/examples/zksync-ethers-example/README.md @@ -0,0 +1,61 @@ +# ZKsync Era zksync-ethers environment example + +This project demonstrates how to compile and deploy your contracts in ZKsync Era using the Hardhat plugins. + +## Prerequisites + +- node.js 14.x or later. +- yarn. + +## Configuration + +Plugin configuration is located in [`hardhat.config.ts`](./hardhat.config.ts). +You should only change the ZKsync network configuration. + +`hardhat.config.ts` example with ZKsync network configured with the name `zkTestnet` and `sepolia` used as the underlying layer 1 network: +```ts +import "@matterlabs/hardhat-zksync-deploy"; +import { HardhatUserConfig } from 'hardhat/types'; + +const config: HardhatUserConfig = { + networks: { + sepolia: { + url: 'https://sepolia.infura.io/v3/' // you can use either the URL of the Ethereum Web3 RPC, or the identifier of the network (e.g. `mainnet` or `rinkeby`) + }, + zkTestnet: { + url: 'https://sepolia.era.zksync.dev', // you should use the URL of the ZkSync network RPC + ethNetwork: 'sepolia', + zksync: true + }, + } +}; + +export default config; +``` + +## Usage + +Before using plugins, you need to build them first + +```sh +# Run the following in the *root* of the repo. +yarn +yarn build +``` + +After that you should be able to run plugins: + +```sh +# Run the following in `examples/ethers-example` folder. +yarn +yarn hardhat compile +yarn hardhat deploy-zksync +``` + +- `yarn hardhat compile`: compiles all the contracts in the `contracts` folder. +- `yarn hardhat deploy-zksync`: runs all the deploy scripts in the `deploy` folder. + - To run a specific script, add the `--script` argument, e.g. `--script 001_deploy.ts`. + - To run on a specific ZKsync network, use standard hardhat `--network` argument, e.g. `--network zkTestnet` + (with `zkTestnet` network specified in the `hardhat.config` networks section, with the `zksync` flag set to `true` and `ethNetwork` specified). + +If you don't specify ZKsync network (`--network`), `local-setup` with (Ethereum RPC URL) and (ZKsync RPC URL) will be used. \ No newline at end of file diff --git a/examples/zksync-ethers-example/contracts/Greeter.sol b/examples/zksync-ethers-example/contracts/Greeter.sol new file mode 100644 index 000000000..a83b09fa6 --- /dev/null +++ b/examples/zksync-ethers-example/contracts/Greeter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +pragma abicoder v2; + +contract Greeter { + string greeting; + constructor(string memory _greeting) { + greeting = _greeting; + } + + function greet() public view returns (string memory) { + return greeting; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + } +} \ No newline at end of file diff --git a/examples/zksync-ethers-example/deploy/001_deploy.ts b/examples/zksync-ethers-example/deploy/001_deploy.ts new file mode 100644 index 000000000..c375a12ba --- /dev/null +++ b/examples/zksync-ethers-example/deploy/001_deploy.ts @@ -0,0 +1,49 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import chalk from 'chalk'; +import { Wallet } from 'zksync-ethers'; + +export default async function (hre: HardhatRuntimeEnvironment) { + console.info(chalk.yellow(`Running deploy`)); + const wallet = await hre.zksyncEthers.getWallet(4); + + // console.info(chalk.yellow(`Depositing to wallet: ${await wallet.getAddress()}`)); + // const depositHandle = await wallet.deposit({ + // to: wallet.address, + // token: utils.ETH_ADDRESS, + // amount: ethers.parseEther('0.001'), + // }); + // await depositHandle.wait(); + + const artifact = await hre.zksyncEthers.loadArtifact('Greeter'); + const greets = await hre.zksyncEthers.deployContract(artifact, ['Hello, world with loadArtifact!'], wallet); + const wallet1 = greets.signer as Wallet; + console.info(chalk.yellow(`Deploying Greeter with wallet: ${await wallet1.getAddress()}`)); + console.info(chalk.green(`Greeter deployed to: ${greets.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets.greet()}`)); + const tx1 = await greets.setGreeting('Hello, world again with loadArtifact!'); + await tx1.wait(); + console.info(chalk.green(`Greeter greeting set to: ${await greets.greet()}`)); + + const greeterFactory = await hre.zksyncEthers.getContractFactory(artifact.abi, artifact.bytecode); + const greeter = await greeterFactory.deploy('Hello, world with abi and bytecode!'); + const wallet2 = greeter.signer as Wallet; + console.info(chalk.yellow(`Deploying Greeter with wallet: ${await wallet2.getAddress()}`)); + console.info(chalk.green(`Greeter deployed to: ${greeter.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greeter.greet()}`)); + const tx = await greeter.setGreeting('Hello, world again with abi and bytecode!'); + await tx.wait(); + console.info(chalk.green(`Greeter greeting set to: ${await greeter.greet()}`)); + + const greeterFactoryFromName = await hre.zksyncEthers.getContractFactory( + 'Greeter', + await hre.zksyncEthers.getWallet('0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1'), + ); + const greeterFromName = await greeterFactoryFromName.deploy('Hello, world with name!'); + const wallet3 = greeterFromName.signer as Wallet; + console.info(chalk.yellow(`Deploying Greeter with wallet: ${await wallet3.getAddress()}`)); + console.info(chalk.green(`Greeter deployed to: ${greeterFromName.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greeterFromName.greet()}`)); + const tx2 = await greeter.setGreeting('Hello, world again with name!'); + await tx2.wait(); + console.info(chalk.green(`Greeter greeting set to: ${await greeter.greet()}`)); +} diff --git a/examples/zksync-ethers-example/deploy/002_deploy.ts b/examples/zksync-ethers-example/deploy/002_deploy.ts new file mode 100644 index 000000000..c2e8c49dc --- /dev/null +++ b/examples/zksync-ethers-example/deploy/002_deploy.ts @@ -0,0 +1,99 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import chalk from 'chalk'; +import { Signer } from 'ethers'; + +export default async function (hre: HardhatRuntimeEnvironment) { + console.info(chalk.yellow(`Running deploy script 002_deploy.ts`)); + + // Deploy Greeter contract with provided signer and name + const signer = await hre.ethers.getSigner('0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'); + const greets = await hre.ethers.deployContract('Greeter', ['Hello, world with loadArtifact!'], signer); + await greets.deployed(); + const greetsRunner = greets.signer as Signer; + console.info(chalk.green(`Greeter deployed to: ${greets.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets.greet()}`)); + const tx1 = await greets.setGreeting('Hello, world again with loadArtifact!'); + await tx1.wait(); + console.info(chalk.green(`Greeter greeting set to: ${await greets.greet()}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greetsRunner.getAddress()}`)); + + console.log('----------------------------------'); + + console.log('Greeter contract deployed with name factory'); + const greeterFactory1 = await hre.ethers.getContractFactory('Greeter', signer); + const greets2 = await greeterFactory1.deploy('Hello, world with name!'); + await greets2.deployed(); + const greets2Runner = greets2.signer as Signer; + console.info(chalk.green(`Greeter deployed to: ${greets2.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets2.greet()}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets2Runner.getAddress()}`)); + + console.log('----------------------------------'); + + console.log('Greeter contract deployed with abi and bytecode'); + const artifact = await hre.artifacts.readArtifact('Greeter'); + const greeterFactory2 = await hre.ethers.getContractFactory(artifact.abi, artifact.bytecode); + const greets3 = await greeterFactory2.deploy('Hello, world with abi and bytecode!'); + await greets3.deployed(); + const greets3Runner = greets3.signer as Signer; + console.info(chalk.green(`Greeter deployed to: ${greets3.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets3.greet()}`)); + const tx = await greets3.setGreeting('Hello, world again with abi and bytecode!'); + await tx.wait(); + console.info(chalk.green(`Greeter greeting set to: ${await greets3.greet()}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets3Runner.getAddress()}`)); + + console.log('----------------------------------'); + + console.log('Greeter contract deployed with artifact'); + const greeterFactory3 = await hre.ethers.getContractFactoryFromArtifact(artifact); + const greets4 = await greeterFactory3.deploy('Hello, world with artifact!'); + await greets4.deployed(); + const greets4Runner = greets4.signer as Signer; + console.info(chalk.green(`Greeter deployed to: ${greets4.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets4.greet()}`)); + const tx2 = await greets4.setGreeting('Hello, world again with artifact!'); + await tx2.wait(); + console.info(chalk.green(`Greeter greeting set to: ${await greets4.greet()}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets4Runner.getAddress()}`)); + + console.log('----------------------------------'); + + console.log('Greeter contract deployed with factory and signer2'); + const [, , signer2] = await hre.ethers.getSigners(); + const greeterFactory4 = await hre.ethers.getContractFactory('Greeter', signer2); + const greets5 = await greeterFactory4.deploy('Hello, world with name!'); + await greets5.deployed(); + const greets5Runner = greets5.signer as Signer; + console.info(chalk.green(`Greeter deployed to: ${greets5.address}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets5.greet()}`)); + const tx3 = await greets5.setGreeting('Hello, world again with name!'); + await tx3.wait(); + console.info(chalk.green(`Greeter greeting set to: ${await greets5.greet()}`)); + console.info(chalk.green(`Greeter greeting set to: ${await greets5Runner.getAddress()}`)); + + console.log('----------------------------------'); + + console.log('Greeter get contract with name'); + const signer3 = hre.ethers.provider.getSigner('0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'); + const contract1 = await hre.ethers.getContractAt('Greeter', greets2.address, signer3); + const contract1Runner = contract1.signer as Signer; + console.info(chalk.green(`Greeter from getContractAt set to: ${await contract1.greet()}`)); + console.info(chalk.green('Runner from getContractAt set to: ', await contract1Runner.getAddress())); + + console.log('----------------------------------'); + + console.log('Greeter get contract with abi'); + const contract2 = await hre.ethers.getContractAt(artifact.abi, greets3.address); + console.info(chalk.green(`Greeter from getContractAt set to: ${await contract2.greet()}`)); + const contract2Runner = contract2.signer as Signer; + console.info(chalk.green('Runner from getContractAt set to: ', await contract2Runner.getAddress())); + + console.log('----------------------------------'); + + console.log('Greeter get contract with artifact'); + const contract3 = await hre.ethers.getContractAtFromArtifact(artifact, greets4.address); + console.info(chalk.green(`Greeter from getContractAt set to: ${await contract3.greet()}`)); + const contract3Runner = contract3.signer as Signer; + console.info(chalk.green('Runner from getContractAt set to: ', await contract3Runner.getAddress())); +} diff --git a/examples/zksync-ethers-example/hardhat.config.ts b/examples/zksync-ethers-example/hardhat.config.ts new file mode 100644 index 000000000..0ac10049c --- /dev/null +++ b/examples/zksync-ethers-example/hardhat.config.ts @@ -0,0 +1,32 @@ +import '@matterlabs/hardhat-zksync-deploy'; +import '@matterlabs/hardhat-zksync-solc'; +import '@matterlabs/hardhat-zksync-ethers'; + +import { HardhatUserConfig } from 'hardhat/config'; + +const config: HardhatUserConfig = { + zksolc: { + compilerSource: 'binary', + settings: { + enableEraVMExtensions: true, + optimizer: { + enabled: true, + }, + } + }, + defaultNetwork: 'zkSyncLocal', + networks: { + zkSyncLocal: { + zksync: true, + url: "http://0.0.0.0:3050", + ethNetwork: 'http://0.0.0.0:8545', + }, + }, + // Docker image only works for solidity ^0.8.0. + // For earlier versions you need to use binary releases of zksolc. + solidity: { + version: '0.8.17', + }, +}; + +export default config; \ No newline at end of file diff --git a/examples/zksync-ethers-example/package.json b/examples/zksync-ethers-example/package.json new file mode 100644 index 000000000..b2a5cd7a9 --- /dev/null +++ b/examples/zksync-ethers-example/package.json @@ -0,0 +1,47 @@ +{ + "name": "hardhat-zksync-ethers-example", + "version": "0.1.0", + "author": "Matter Labs", + "license": "MIT", + "scripts": { + "lint": "pnpm eslint", + "prettier:check": "pnpm prettier --check", + "lint:fix": "pnpm eslint --fix", + "fmt": "pnpm prettier --write", + "eslint": "eslint deploy/*.ts", + "prettier": "prettier deploy/*.ts", + "test": "mocha test/tests.ts --exit", + "build": "tsc --build .", + "clean": "rimraf dist" + }, + "devDependencies": { + "@types/node": "^18.11.17", + "@typescript-eslint/eslint-plugin": "^7.12.0", + "@typescript-eslint/parser": "^7.12.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-no-only-tests": "^3.1.0", + "eslint-plugin-prettier": "^5.0.1", + "prettier": "^3.3.0", + "rimraf": "^5.0.7", + "ts-node": "^10.9.2", + "typescript": "^5.3.0" + }, + "dependencies": { + "@matterlabs/hardhat-zksync-deploy": "workspace:^", + "@matterlabs/hardhat-zksync-solc": "^1.2.1", + "@matterlabs/hardhat-zksync-ethers": "workspace:^", + "chalk": "^4.1.2", + "hardhat": "^2.14.0", + "ethers": "~5.7.2", + "zksync-ethers": "^5.8.0" + }, + "prettier": { + "tabWidth": 4, + "printWidth": 120, + "parser": "typescript", + "singleQuote": true, + "bracketSpacing": true + } +} \ No newline at end of file diff --git a/examples/zksync-ethers-example/tsconfig.json b/examples/zksync-ethers-example/tsconfig.json new file mode 100644 index 000000000..27149c97d --- /dev/null +++ b/examples/zksync-ethers-example/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "forceConsistentCasingInFileNames": true, + "outDir": "dist" + }, + "include": [ + "./hardhat.config.ts", + "./scripts", + "./deploy", + "./test", + "typechain/**/*" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index b36784442..c7999ab44 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,8 @@ "typescript": "^5.3.0" }, "scripts": { - "build": "tsc --build packages/hardhat-zksync-deploy packages/hardhat-zksync-upgradable packages/hardhat-zksync", - "watch": "tsc --build --watch packages/hardhat-zksync-deploy packages/hardhat-zksync-upgradable packages/hardhat-zksync", + "build": "tsc --build packages/hardhat-zksync-deploy packages/hardhat-zksync-upgradable packages/hardhat-zksync-ethers packages/hardhat-zksync", + "watch": "tsc --build --watch packages/hardhat-zksync-deploy packages/hardhat-zksync-upgradable packages/hardhat-zksync-ethers packages/hardhat-zksync", "clean": "pnpm run --recursive clean", "lint": "pnpm run --recursive lint", "lint:fix": "pnpm run --recursive lint:fix", diff --git a/packages/hardhat-zksync-ethers/.eslintrc.js b/packages/hardhat-zksync-ethers/.eslintrc.js new file mode 100644 index 000000000..56ca0b4bd --- /dev/null +++ b/packages/hardhat-zksync-ethers/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: [`${__dirname}/../../config/eslint/eslintrc.cjs`], + parserOptions: { + project: `${__dirname}/eslint-tsconfig.json`, + sourceType: "module", + }, +}; diff --git a/packages/hardhat-zksync-ethers/.mocharc.json b/packages/hardhat-zksync-ethers/.mocharc.json new file mode 100644 index 000000000..d00ceb413 --- /dev/null +++ b/packages/hardhat-zksync-ethers/.mocharc.json @@ -0,0 +1,5 @@ +{ + "require": "ts-node/register/files", + "ignore": ["test/fixture-projects/**/*"], + "timeout": 10000 +} diff --git a/packages/hardhat-zksync-ethers/.prettierignore b/packages/hardhat-zksync-ethers/.prettierignore new file mode 100644 index 000000000..37cbd4e3f --- /dev/null +++ b/packages/hardhat-zksync-ethers/.prettierignore @@ -0,0 +1,5 @@ +/node_modules +/dist +/test/fixture-projects/**/artifacts +/test/fixture-projects/**/cache +CHANGELOG.md diff --git a/packages/hardhat-zksync-ethers/CHANGELOG.md b/packages/hardhat-zksync-ethers/CHANGELOG.md new file mode 100644 index 000000000..c42b332f1 --- /dev/null +++ b/packages/hardhat-zksync-ethers/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +## [1.1.0](https://github.com/matter-labs/hardhat-zksync/compare/@matterlabs/hardhat-zksync-ethers-v1.0.0...@matterlabs/hardhat-zksync-ethers-v1.1.0) (2024-06-19) + + +### Features + +* bump ethers, zksync-ethers, hardaht and other dependencies to newer versions ([#1111](https://github.com/matter-labs/hardhat-zksync/issues/1111)) ([a2d503a](https://github.com/matter-labs/hardhat-zksync/commit/a2d503abe3f504859651f22998046576eddf6579)) +* bump hardhat-zksync-deploy dependency version ([#990](https://github.com/matter-labs/hardhat-zksync/issues/990)) ([76362bf](https://github.com/matter-labs/hardhat-zksync/commit/76362bf435a2af5294a9106370f9c9faaaccdd17)) + +## [1.0.0](https://github.com/matter-labs/hardhat-zksync/compare/@matterlabs/hardhat-zksync-ethers-v0.0.1-beta.2...@matterlabs/hardhat-zksync-ethers-v1.0.0) (2024-01-25) + + +### Features + +* L1 provider support in ethers plugin ([#666](https://github.com/matter-labs/hardhat-zksync/issues/666)) ([24caec5](https://github.com/matter-labs/hardhat-zksync/commit/24caec58a9c84cee357ec08e9f8c9548ce49c5a2)) diff --git a/packages/hardhat-zksync-ethers/README.md b/packages/hardhat-zksync-ethers/README.md new file mode 100644 index 000000000..4f11f2ef5 --- /dev/null +++ b/packages/hardhat-zksync-ethers/README.md @@ -0,0 +1,74 @@ +# hardhat-zksync-ethers 🚀 + +ZKsync Era [Hardhat](https://hardhat.org/) plugin that is a wrapper around [zksync-ethers](https://www.npmjs.com/package/zksync-ethers) sdk that gives additional methods to use for faster development. + +![Era Logo](https://github.com/matter-labs/era-contracts/raw/main/eraLogo.svg) + +## 📥 Installation + +To install **hardhat-zksync-ethers** plugin, run: + +`npm install -D @matterlabs/hardhat-zksync-ethers` + +or + +`yarn add -D @matterlabs/hardhat-zksync-ethers zksync-ethers ethers` + +## Helpers + +| 🙏 Helper | 📄 Description | +|-----------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| providerL2 | Retruns a Provider for L2 automatically connected to the selected network. | +| providerL1 | Retruns a Provider for L1 automatically connected to the selected network. | +| getWallet | Returns Wallet for the given private key or index. | +| getContractFactory variant1 | Returns a ContractFactory for provided artifact name. | +| getContractFactory variant2 | Returns a ContractFactory for provided artifact abi and bytecode. | +| getContractFactoryFromArtifact | Returns a ContractFactory for provided artifact. | +| getContractAt | Returns Contract for provided artifact name or abi and address of deployed contract. | +| getContractAtFromArtifact | Returns ContractFactory for provided artifact and address of deployed contract | +| getImpersonatedSigner | Impersonates Signer from address | +| extractFactoryDeps | Extracts factory deps from artifact | +| loadArtifact | Load ZkSyncArtifact from contract name | +| deployContract | Deploys a contract to the network | + +## 📖 Example + +After installing it, add the plugin to your Hardhat config: + +`import "@matterlabs/hardhat-zksync-ethers";` + +This plugin extends hardhat runtime environment, use it like this: + +Retrieve your contract factory: + +`const myContractFactory = await hre.zksyncEthers.getContractFactory("MyContract");` + +Deploy your contract: + +`const myContract = await myContractFactory.deploy("Hello, world!");` + +Find deployed address: + +`console.info(myContract.address);` + +## 📝 Documentation + +In addition to the [hardhat-zksync-ethers](https://era.zksync.io/docs/tools/hardhat/hardhat-zksync-ethers.html), ZKsync's Era [website](https://era.zksync.io/docs/) offers a variety of resources including: + +[Guides to get started](https://era.zksync.io/docs/dev/building-on-zksync/hello-world.html): Learn how to start building on ZKsync Era.\ +[Hardhat ZKsync Era plugins](https://era.zksync.io/docs/tools/hardhat/getting-started.html): Overview and guides for all Hardhat ZKsync Era plugins.\ +[Hyperscaling](https://era.zksync.io/docs/reference/concepts/hyperscaling.html#what-are-hyperchains): Deep dive into hyperscaling on ZKsync Era. + +## 🤝 Contributing + +Contributions are always welcome! Feel free to open any issue or send a pull request. + +Go to [CONTRIBUTING.md](https://github.com/matter-labs/hardhat-zksync/blob/main/.github/CONTRIBUTING.md) to learn about steps and best practices for contributing to ZKsync hardhat tooling base repository. + + +## 🙌 Feedback, help and news + +[ZKsync Era Discord server](https://join.zksync.dev/): for questions and feedback.\ +[Follow ZKsync Era on Twitter](https://twitter.com/zksync) + +## Happy building! 👷‍♀️👷‍♂️ \ No newline at end of file diff --git a/packages/hardhat-zksync-ethers/eslint-tsconfig.json b/packages/hardhat-zksync-ethers/eslint-tsconfig.json new file mode 100644 index 000000000..c86edc88a --- /dev/null +++ b/packages/hardhat-zksync-ethers/eslint-tsconfig.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "./dist" + ], + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/packages/hardhat-zksync-ethers/package.json b/packages/hardhat-zksync-ethers/package.json new file mode 100644 index 000000000..ce76ff1aa --- /dev/null +++ b/packages/hardhat-zksync-ethers/package.json @@ -0,0 +1,80 @@ +{ + "name": "@matterlabs/hardhat-zksync-ethers", + "version": "0.1.0", + "description": "Hardhat plugin for integration with zksync-ethers library", + "repository": "github:matter-labs/hardhat-zksync", + "homepage": "https://github.com/matter-labs/hardhat-zksync/tree/main/packages/hardhat-zksync-ethers", + "author": "Matter Labs", + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "keywords": [ + "ethereum", + "smart-contracts", + "hardhat", + "hardhat-plugin", + "ZKsync", + "zksync-ethers" + ], + "scripts": { + "lint": "pnpm eslint", + "prettier:check": "pnpm prettier --check", + "lint:fix": "pnpm eslint --fix", + "fmt": "pnpm prettier --write", + "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", + "prettier": "prettier 'src/**/*.ts' 'test/**/*.ts'", + "test": "NODE_ENV=test c8 mocha test/**/*.ts --no-timeout --exit", + "build": "tsc --build .", + "clean": "rimraf dist" + }, + "files": [ + "dist/", + "src/", + "LICENSE", + "README.md" + ], + "dependencies": { + "chalk": "^4.1.2", + "hardhat": "^2.14.0", + "chai": "^4.3.4", + "@matterlabs/hardhat-zksync-solc": "^1.2.1", + "@matterlabs/hardhat-zksync-deploy": "workspace:^", + "@nomiclabs/hardhat-ethers": "^2.2.3" + }, + "devDependencies": { + "@types/chai": "^4.3.16", + "@types/chai-as-promised": "^7.1.8", + "@types/lodash.isequal": "^4.5.8", + "@types/mocha": "^10.0.6", + "@types/node": "^18.0.0", + "@types/sinon": "^17.0.3", + "@typescript-eslint/eslint-plugin": "^7.12.0", + "@typescript-eslint/parser": "7.12.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-no-only-tests": "^3.1.0", + "eslint-plugin-prettier": "^5.0.1", + "mocha": "^10.4.0", + "sinon": "^18.0.0", + "prettier": "^3.3.0", + "rimraf": "^5.0.7", + "ts-node": "^10.9.2", + "typescript": "^5.3.0", + "ethers": "~5.7.2", + "zksync-ethers": "^5.8.0", + "rlp": "3.0.0", + "c8": "^8.0.1" + }, + "peerDependencies": { + "ethers": "^6.12.2", + "zksync-ethers": "^6.8.0" + }, + "prettier": { + "tabWidth": 4, + "printWidth": 120, + "parser": "typescript", + "singleQuote": true, + "bracketSpacing": true + } +} diff --git a/packages/hardhat-zksync-ethers/src/constants.ts b/packages/hardhat-zksync-ethers/src/constants.ts new file mode 100644 index 000000000..3a349fe07 --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/constants.ts @@ -0,0 +1,18 @@ +export const PLUGIN_NAME = '@matterlabs/hardhat-zksync-ethers'; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export enum LOCAL_CHAIN_IDS_ENUM { + ERA_NODE = '0x104', + LOCAL_SETUP = '0x10e', +} + +export const LOCAL_CHAIN_IDS = [LOCAL_CHAIN_IDS_ENUM.ERA_NODE, LOCAL_CHAIN_IDS_ENUM.LOCAL_SETUP]; + +export const LOCAL_CHAINS_WITH_IMPERSONATION = [LOCAL_CHAIN_IDS_ENUM.ERA_NODE]; + +export const ZKSOLC_ARTIFACT_FORMAT_VERSION = 'hh-zksolc-artifact-1'; +export const ZKVYPER_ARTIFACT_FORMAT_VERSION = 'hh-zkvyper-artifact-1'; + +export const ETH_DEFAULT_NETWORK_RPC_URL = 'http://0.0.0.0:8545'; + +export const SUPPORTED_L1_TESTNETS = ['mainnet', 'rinkeby', 'ropsten', 'kovan', 'goerli', 'sepolia']; diff --git a/packages/hardhat-zksync-ethers/src/errors.ts b/packages/hardhat-zksync-ethers/src/errors.ts new file mode 100644 index 000000000..10ea1969a --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/errors.ts @@ -0,0 +1,8 @@ +import { HardhatPluginError } from 'hardhat/plugins'; +import { PLUGIN_NAME } from './constants'; + +export class ZkSyncEthersPluginError extends HardhatPluginError { + constructor(message: string, parentError?: Error) { + super(PLUGIN_NAME, message, parentError); + } +} diff --git a/packages/hardhat-zksync-ethers/src/extension-generator.ts b/packages/hardhat-zksync-ethers/src/extension-generator.ts new file mode 100644 index 000000000..07a629181 --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/extension-generator.ts @@ -0,0 +1,75 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { Address, DeploymentType } from 'zksync-ethers/build/types'; +import { lazyObject } from 'hardhat/plugins'; +import { Overrides, BytesLike } from 'ethers'; +import { createProviders } from './utils'; +import { + deployContract, + extractFactoryDeps, + getContractAtFromArtifact, + getContractFactoryFromArtifact, + getImpersonatedSigner, + getSigner, + getSigners, + getWallet, + getWallets, + loadArtifact, + makeContractAt, + makeGetContractFactory, +} from './helpers'; +import { HardhatZksyncSignerOrWallet, HardhatZksyncSignerOrWalletOrFactoryOptions, ZkSyncArtifact } from './types'; +import { Generator } from './generator'; + +export class ZkSyncGenerator implements Generator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + public populateExtension(): any { + return lazyObject(() => { + const { zksyncEthers } = require('zksync-ethers'); + const { ethWeb3Provider, zkWeb3Provider } = createProviders(this._hre); + const { ethers } = require('ethers'); + + return { + ...ethers, + ...zksyncEthers, + providerL1: ethWeb3Provider, + providerL2: zkWeb3Provider, + provider: zkWeb3Provider, + getSigners: () => getSigners(this._hre), + getSigner: (address: string) => getSigner(this._hre, address), + getWallet: (privateKeyOrIndex?: string | number) => getWallet(this._hre, privateKeyOrIndex), + getWallets: () => getWallets(this._hre), + getImpersonatedSigner: (address: string) => getImpersonatedSigner(this._hre, address), + getContractFactory: makeGetContractFactory(this._hre), + getContractFactoryFromArtifact: ( + artifact: ZkSyncArtifact, + walletOrSignerOrOptions?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, + ) => getContractFactoryFromArtifact(this._hre, artifact, walletOrSignerOrOptions, deploymentType), + getContractAt: makeContractAt(this._hre), + getContractAtFromArtifact: ( + artifact: ZkSyncArtifact, + address: string | Address, + walletOrSigner?: HardhatZksyncSignerOrWallet, + ) => getContractAtFromArtifact(this._hre, artifact, address, walletOrSigner), + extractFactoryDeps: (artifact: ZkSyncArtifact) => extractFactoryDeps(this._hre, artifact), + loadArtifact: (name: string) => loadArtifact(this._hre, name), + deployContract: ( + artifact: ZkSyncArtifact, + constructorArguments: any[], + walletOrSigner?: HardhatZksyncSignerOrWallet, + overrides?: Overrides, + additionalFactoryDeps?: BytesLike[], + ) => + deployContract( + this._hre, + artifact, + constructorArguments, + walletOrSigner, + overrides, + additionalFactoryDeps, + ), + }; + }); + } +} diff --git a/packages/hardhat-zksync-ethers/src/generator.ts b/packages/hardhat-zksync-ethers/src/generator.ts new file mode 100644 index 000000000..875dc519d --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/generator.ts @@ -0,0 +1,21 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { ZkSyncGenerator } from './extension-generator'; +import { EthersGenerator } from './hardhat-ethers/extension-generator'; + +export class ExtensionGenerator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + public populatedExtension(): any { + if (this._hre.network.zksync) { + const zkSyncGenerator = new ZkSyncGenerator(this._hre); + return zkSyncGenerator.populateExtension(); + } + + const ethersGenerators = new EthersGenerator(this._hre); + return ethersGenerators.populateExtension(); + } +} + +export interface Generator { + populateExtension(): any; +} diff --git a/packages/hardhat-zksync-ethers/src/hardhat-ethers/extension-generator.ts b/packages/hardhat-zksync-ethers/src/hardhat-ethers/extension-generator.ts new file mode 100644 index 000000000..fd1790c56 --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/hardhat-ethers/extension-generator.ts @@ -0,0 +1,32 @@ +import { lazyObject } from 'hardhat/plugins'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { Generator } from '../generator'; + +export class EthersGenerator implements Generator { + constructor(private _hre: HardhatRuntimeEnvironment) {} + + public populateExtension(): any { + return lazyObject(() => { + const hardhatEthersHelpers = require('@nomiclabs/hardhat-ethers/internal/helpers'); + const { createProviderProxy } = require('@nomiclabs/hardhat-ethers/internal/provider-proxy'); + const { ethers } = require('ethers'); + const provider = new createProviderProxy(this._hre.network.provider); + return { + ...ethers, + provider, + getSigner: (address: string) => hardhatEthersHelpers.getSigner(this._hre, address), + getSigners: () => hardhatEthersHelpers.getSigners(this._hre), + getImpersonatedSigner: (address: string) => + hardhatEthersHelpers.getImpersonatedSigner(this._hre, address), + getContractFactory: hardhatEthersHelpers.getContractFactory.bind(null, this._hre) as any, + getContractFactoryFromArtifact: hardhatEthersHelpers.getContractFactoryFromArtifact.bind( + null, + this._hre, + ), + getContractAt: hardhatEthersHelpers.getContractAt.bind(null, this._hre), + getContractAtFromArtifact: hardhatEthersHelpers.getContractAtFromArtifact.bind(null, this._hre), + deployContract: hardhatEthersHelpers.deployContract.bind(null, this._hre) as any, + }; + }); + } +} diff --git a/packages/hardhat-zksync-ethers/src/hardhat-zksync-provider.ts b/packages/hardhat-zksync-ethers/src/hardhat-zksync-provider.ts new file mode 100644 index 000000000..fe229c9c1 --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/hardhat-zksync-provider.ts @@ -0,0 +1,23 @@ +import { ethers } from 'ethers'; +import { Provider } from 'zksync-ethers'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { ConnectionInfo } from 'ethers/lib/utils'; +import { HardhatZksyncSigner } from './signers/hardhat-zksync-signer'; + +export class HardhatZksyncEthersProvider extends Provider { + constructor( + public readonly hre: HardhatRuntimeEnvironment, + url?: ConnectionInfo | string, + network?: ethers.providers.Networkish, + ) { + if (!url) { + url = 'http://localhost:3050'; + } + + super(url, network); + } + + public getSigner(address?: string | number | undefined): HardhatZksyncSigner { + return HardhatZksyncSigner.from(super.getSigner(address) as any, this); + } +} diff --git a/packages/hardhat-zksync-ethers/src/helpers.ts b/packages/hardhat-zksync-ethers/src/helpers.ts new file mode 100644 index 000000000..ab03de01d --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/helpers.ts @@ -0,0 +1,277 @@ +import { Contract, ContractFactory, Wallet } from 'zksync-ethers'; + +import * as ethers from 'ethers'; + +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +import { Address, DeploymentType } from 'zksync-ethers/build/types'; +import { + GetContractAt, + GetContractAtFromAbi, + GetContractAtFromArtifact, + GetContractAtFromName, + GetContractFactory, + GetContractFactoryAbiBytecode, + GetContractFactoryArtifact, + GetContractFactoryArtifactName, + HardhatZksyncSignerOrWallet, + HardhatZksyncSignerOrWalletOrFactoryOptions, + ZkSyncArtifact, +} from './types'; +import { ZkSyncEthersPluginError } from './errors'; +import { getSignerAccounts, getSignerOrWallet, getWalletsFromAccount, isArtifact, isNumber, isString } from './utils'; +import { ZKSOLC_ARTIFACT_FORMAT_VERSION, ZKVYPER_ARTIFACT_FORMAT_VERSION } from './constants'; +import { HardhatZksyncSigner } from './signers/hardhat-zksync-signer'; + +export async function getWallet(hre: HardhatRuntimeEnvironment, privateKeyOrIndex?: string | number): Promise { + const privateKey = isString(privateKeyOrIndex) ? (privateKeyOrIndex as string) : undefined; + const accountNumber = isNumber(privateKeyOrIndex) ? (privateKeyOrIndex as number) : undefined; + + if (privateKey) { + return new Wallet(privateKey, hre.ethers.provider).connectToL1(hre.ethers.providerL1); + } + + const accounts = hre.network.config.accounts; + + const wallets = await getWalletsFromAccount(hre, accounts); + + if (accountNumber && accountNumber >= wallets.length) { + throw new ZkSyncEthersPluginError('Account private key with specified index is not found'); + } + + if (wallets.length === 0) { + throw new ZkSyncEthersPluginError('Accounts are not configured for this network'); + } + + return wallets[accountNumber || 0]; +} + +export async function getWallets(hre: HardhatRuntimeEnvironment): Promise { + const accounts = hre.network.config.accounts; + + return await getWalletsFromAccount(hre, accounts); +} + +export async function getSigners(hre: HardhatRuntimeEnvironment): Promise { + const accounts: string[] = await getSignerAccounts(hre); + + const signersWithAddress = await Promise.all(accounts.map((account) => getSigner(hre, account))); + + return signersWithAddress; +} + +export async function getSigner(hre: HardhatRuntimeEnvironment, address: string): Promise { + return hre.ethers.providerL2.getSigner(address) as HardhatZksyncSigner; +} + +export async function getImpersonatedSigner( + hre: HardhatRuntimeEnvironment, + address: string, +): Promise { + await hre.ethers.provider.send('hardhat_impersonateAccount', [address]); + return await getSigner(hre, address); +} + +export function makeGetContractFactory(hre: HardhatRuntimeEnvironment): GetContractFactory { + return async function ( + ...args: Parameters + ): Promise { + if (isArtifact(args[0])) { + return getContractFactoryFromArtifact(hre, ...(args as Parameters)); + } + + if (args[0] instanceof Array && ethers.utils.isBytesLike(args[1])) { + return getContractFactoryByAbiAndBytecode(hre, ...(args as Parameters)); + } + + if (typeof args[0] === 'string') { + const artifact = await loadArtifact(hre, args[0] as string); + + return getContractFactoryFromArtifact( + hre, + artifact, + args[1] as HardhatZksyncSignerOrWalletOrFactoryOptions, + args[2] as DeploymentType, + ); + } + + throw new ZkSyncEthersPluginError( + `You are trying to create a contract factory, but you have not passed a valid parameter.`, + ); + }; +} + +export async function getContractFactoryFromArtifact( + hre: HardhatRuntimeEnvironment, + artifact: ZkSyncArtifact, + walletOrSignerOrOptions?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, +): Promise { + if (!isArtifact(artifact)) { + throw new ZkSyncEthersPluginError( + `You are trying to create a contract factory from an artifact, but you have not passed a valid artifact parameter.`, + ); + } + + if (artifact.bytecode === '0x') { + throw new ZkSyncEthersPluginError( + `You are trying to create a contract factory for the contract ${artifact.contractName}, which is abstract and can't be deployed. +If you want to call a contract using ${artifact.contractName} as its interface use the "getContractAt" function instead.`, + ); + } + + return getContractFactoryByAbiAndBytecode( + hre, + artifact.abi, + artifact.bytecode, + walletOrSignerOrOptions, + deploymentType, + ); +} + +async function getContractFactoryByAbiAndBytecode( + hre: HardhatRuntimeEnvironment, + abi: any[], + bytecode: ethers.BytesLike, + walletOrSignerOrOptions?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, +): Promise { + let walletOrSigner: HardhatZksyncSignerOrWallet | undefined = getSignerOrWallet(walletOrSignerOrOptions); + + if (!walletOrSigner) { + walletOrSigner = (await getSigners(hre))[0]; + } + + return new ContractFactory(abi, bytecode, walletOrSigner, deploymentType); +} + +export function makeContractAt(hre: HardhatRuntimeEnvironment): GetContractAt { + return async function getContractAt( + ...args: Parameters + ): Promise { + if (isArtifact(args[0])) { + return getContractAtFromArtifact(hre, ...(args as Parameters)); + } + + if (typeof args[0] === 'string') { + const artifact = await loadArtifact(hre, args[0]); + return getContractAtFromArtifact( + hre, + artifact, + args[1] as string | Address, + args[2] as HardhatZksyncSignerOrWallet, + ); + } + + return getContractAtFromAbi(hre, ...(args as Parameters)); + }; +} + +export async function getContractAtFromArtifact( + hre: HardhatRuntimeEnvironment, + artifact: ZkSyncArtifact, + address: string | Address, + walletOrSigner?: HardhatZksyncSignerOrWallet, +): Promise { + return getContractAtFromAbi(hre, artifact.abi, address, walletOrSigner); +} + +async function getContractAtFromAbi( + hre: HardhatRuntimeEnvironment, + abi: any[], + address: string | Address, + walletOrSigner?: HardhatZksyncSignerOrWallet, +): Promise { + if (!walletOrSigner) { + walletOrSigner = (await getSigners(hre))[0]; + } + + let contract = new Contract(address, abi, walletOrSigner); + + if (contract.runner === null) { + contract = contract.connect(hre.ethers.provider) as Contract; + } + + return contract; +} + +export async function deployContract( + hre: HardhatRuntimeEnvironment, + artifactOrContract: ZkSyncArtifact | string, + constructorArguments: any[] = [], + walletOrSigner?: HardhatZksyncSignerOrWallet, + overrides?: ethers.Overrides, + additionalFactoryDeps?: ethers.BytesLike[], +): Promise { + if (!walletOrSigner) { + walletOrSigner = (await getSigners(hre))[0]; + } + + const artifact = + typeof artifactOrContract === 'string' ? await loadArtifact(hre, artifactOrContract) : artifactOrContract; + + const factory = await getContractFactoryFromArtifact(hre, artifact, walletOrSigner); + + const baseDeps = await extractFactoryDeps(hre, artifact); + const additionalDeps = additionalFactoryDeps ? additionalFactoryDeps.map((val) => ethers.utils.hexlify(val)) : []; + const factoryDeps = [...baseDeps, ...additionalDeps]; + + 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; +} + +export async function loadArtifact( + hre: HardhatRuntimeEnvironment, + contractNameOrFullyQualifiedName: string, +): Promise { + const artifact = await hre.artifacts.readArtifact(contractNameOrFullyQualifiedName); + + // Verify that this artifact was compiled by the ZKsync compiler, and not `solc` or `vyper`. + if (artifact._format !== ZKSOLC_ARTIFACT_FORMAT_VERSION && artifact._format !== ZKVYPER_ARTIFACT_FORMAT_VERSION) { + throw new ZkSyncEthersPluginError( + `Artifact ${contractNameOrFullyQualifiedName} was not compiled by zksolc or zkvyper`, + ); + } + return artifact as ZkSyncArtifact; +} + +export async function extractFactoryDeps(hre: HardhatRuntimeEnvironment, artifact: ZkSyncArtifact): Promise { + const visited = new Set(); + visited.add(`${artifact.sourceName}:${artifact.contractName}`); + return await extractFactoryDepsRecursive(hre, artifact, visited); +} + +async function extractFactoryDepsRecursive( + hre: HardhatRuntimeEnvironment, + 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 (!dependencyHash) continue; + const dependencyContract = artifact.factoryDeps[dependencyHash]; + if (!visited.has(dependencyContract)) { + const dependencyArtifact = await loadArtifact(hre, dependencyContract); + factoryDeps.push(dependencyArtifact.bytecode); + visited.add(dependencyContract); + const transitiveDeps = await extractFactoryDepsRecursive(hre, dependencyArtifact, visited); + factoryDeps.push(...transitiveDeps); + } + } + + return factoryDeps; +} diff --git a/packages/hardhat-zksync-ethers/src/index.ts b/packages/hardhat-zksync-ethers/src/index.ts new file mode 100644 index 000000000..3f1a2f327 --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/index.ts @@ -0,0 +1,15 @@ +import { extendEnvironment } from 'hardhat/config'; +import './type-extensions'; +import { ExtensionGenerator } from './generator'; + +extendEnvironment((hre) => { + const extensionGenerator = new ExtensionGenerator(hre); + hre.ethers = extensionGenerator.populatedExtension(); + + if (hre.network.zksync) { + hre.zksyncEthers = extensionGenerator.populatedExtension(); + } +}); + +export { HardhatZksyncEthersProvider } from './hardhat-zksync-provider'; +export { HardhatZksyncSigner } from './signers/hardhat-zksync-signer'; diff --git a/packages/hardhat-zksync-ethers/src/rich-wallets.ts b/packages/hardhat-zksync-ethers/src/rich-wallets.ts new file mode 100644 index 000000000..ca286b158 --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/rich-wallets.ts @@ -0,0 +1,92 @@ +const eraTestNodeRickWallets = [ + { + address: '0xBC989fDe9e54cAd2aB4392Af6dF60f04873A033A', + privateKey: '0x3d3cbc973389cb26f657686445bcc75662b415b656078503592ac8c1abb8810e', + }, + { + address: '0x55bE1B079b53962746B2e86d12f158a41DF294A6', + privateKey: '0x509ca2e9e6acf0ba086477910950125e698d4ea70fa6f63e000c5a22bda9361c', + }, + { + address: '0xCE9e6063674DC585F6F3c7eaBe82B9936143Ba6C', + privateKey: '0x71781d3a358e7a65150e894264ccc594993fbc0ea12d69508a340bc1d4f5bfbc', + }, + { + address: '0xd986b0cB0D1Ad4CCCF0C4947554003fC0Be548E9', + privateKey: '0x379d31d4a7031ead87397f332aab69ef5cd843ba3898249ca1046633c0c7eefe', + }, + { + address: '0x87d6ab9fE5Adef46228fB490810f0F5CB16D6d04', + privateKey: '0x105de4e75fe465d075e1daae5647a02e3aad54b8d23cf1f70ba382b9f9bee839', + }, + { + address: '0x78cAD996530109838eb016619f5931a03250489A', + privateKey: '0x7becc4a46e0c3b512d380ca73a4c868f790d1055a7698f38fb3ca2b2ac97efbb', + }, + { + address: '0xc981b213603171963F81C687B9fC880d33CaeD16', + privateKey: '0xe0415469c10f3b1142ce0262497fe5c7a0795f0cbfd466a6bfa31968d0f70841', + }, + { + address: '0x42F3dc38Da81e984B92A95CBdAAA5fA2bd5cb1Ba', + privateKey: '0x4d91647d0a8429ac4433c83254fb9625332693c848e578062fe96362f32bfe91', + }, + { + address: '0x64F47EeD3dC749d13e49291d46Ea8378755fB6DF', + privateKey: '0x41c9f9518aa07b50cb1c0cc160d45547f57638dd824a8d85b5eb3bf99ed2bdeb', + }, + { + address: '0xe2b8Cb53a43a56d4d2AB6131C81Bd76B86D3AFe5', + privateKey: '0xb0680d66303a0163a19294f1ef8c95cd69a9d7902a4aca99c05f3e134e68a11a', + }, +]; + +const commonRichWallets = [ + { + address: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + privateKey: '0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110', + }, + { + address: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + privateKey: '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3', + }, + { + address: '0x0D43eB5B8a47bA8900d84AA36656c92024e9772e', + privateKey: '0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e', + }, + { + address: '0xA13c10C0D5bd6f79041B9835c63f91de35A15883', + privateKey: '0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8', + }, + { + address: '0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92', + privateKey: '0xf12e28c0eb1ef4ff90478f6805b68d63737b7f33abfa091601140805da450d93', + }, + { + address: '0x4F9133D1d3F50011A6859807C837bdCB31Aaab13', + privateKey: '0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8', + }, + { + address: '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA', + privateKey: '0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959', + }, + { + address: '0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa', + privateKey: '0x74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998', + }, + { + address: '0xe706e60ab5Dc512C36A4646D719b889F398cbBcB', + privateKey: '0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1', + }, + { + address: '0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424', + privateKey: '0x3eb15da85647edd9a1159a4a13b9e7c56877c4eb33f614546d4db06a51868b1c', + }, +]; + +export const richWallets = { + // eslint-disable-next-line @typescript-eslint/naming-convention + '0x104': [...eraTestNodeRickWallets, ...commonRichWallets], + // eslint-disable-next-line @typescript-eslint/naming-convention + '0x10e': [...commonRichWallets], +}; diff --git a/packages/hardhat-zksync-ethers/src/signers/hardhat-zksync-eip712-signer.ts b/packages/hardhat-zksync-ethers/src/signers/hardhat-zksync-eip712-signer.ts new file mode 100644 index 000000000..08986fc0c --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/signers/hardhat-zksync-eip712-signer.ts @@ -0,0 +1,32 @@ +import { ethers } from 'ethers'; +import { EIP712Signer, Wallet } from 'zksync-ethers'; +import { TransactionRequest } from 'zksync-ethers/build/types'; +import { serialize } from 'zksync-ethers/build/utils'; + +export class HardhatZksyncEIP712Signer extends EIP712Signer { + private readonly accountWallet: Wallet; + + constructor(wallet: Wallet, chainId: number | Promise) { + super(wallet, chainId); + this.accountWallet = wallet; + } + + public async signMessage(message: string | Uint8Array): Promise { + return this.accountWallet.signMessage(message); + } + + public async signTransaction(_transaction: TransactionRequest): Promise { + _transaction.customData = _transaction.customData || {}; + _transaction.customData.customSignature = await this.sign(_transaction); + + return serialize(_transaction); + } + + public async _signTypedData( + _domain: ethers.TypedDataDomain, + _types: Record, + _value: Record, + ): Promise { + return this.accountWallet._signTypedData(_domain, _types, _value); + } +} diff --git a/packages/hardhat-zksync-ethers/src/signers/hardhat-zksync-signer.ts b/packages/hardhat-zksync-ethers/src/signers/hardhat-zksync-signer.ts new file mode 100644 index 000000000..c450da19f --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/signers/hardhat-zksync-signer.ts @@ -0,0 +1,153 @@ +import { EIP712Signer, Provider, Signer, Wallet } from 'zksync-ethers'; +import { TransactionRequest, TransactionResponse } from 'zksync-ethers/build/types'; +import { EIP712_TX_TYPE, isAddressEq, serialize } from 'zksync-ethers/build/utils'; +import { ethers } from 'ethers'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { findWalletFromAddress, isImpersonatedSigner } from '../utils'; +import { HardhatZksyncEthersProvider } from '../hardhat-zksync-provider'; +import { richWallets } from '../rich-wallets'; +import { LOCAL_CHAIN_IDS_ENUM } from '../constants'; +import { HardhatZksyncEIP712Signer } from './hardhat-zksync-eip712-signer'; + +export class HardhatZksyncSigner extends Signer { + private accountWallet?: Wallet | HardhatZksyncEIP712Signer | undefined; + + public static from( + signer: ethers.providers.JsonRpcSigner & { provider: HardhatZksyncEthersProvider }, + zksyncProvider?: Provider | HardhatZksyncEthersProvider, + ): HardhatZksyncSigner { + const newSigner: Signer = super.from(signer, zksyncProvider); + const hardhatZksyncSigner: HardhatZksyncSigner = Object.setPrototypeOf( + newSigner, + HardhatZksyncSigner.prototype, + ); + return hardhatZksyncSigner; + } + + public async sendTransaction(transaction: TransactionRequest): Promise { + if (!this.accountWallet) { + this.accountWallet = await HardhatZksyncSigner._getProperSigner( + (this.provider as HardhatZksyncEthersProvider).hre, + this._address, + ); + } + + const address = await this.getAddress(); + const from = !transaction.from ? address : ethers.utils.getAddress(transaction.from); + + if (!isAddressEq(from, address)) { + throw new Error('Transaction `from` address mismatch!'); + } + + transaction.from = from; + + if (!this.accountWallet) { + throw new Error(`Account ${from} is not managed by the node you are connected to.`); + } + + if (this.accountWallet instanceof EIP712Signer) { + return this._sendTransaction(transaction); + } + + return this.accountWallet.sendTransaction(transaction); + } + + public async signMessage(message: string | Uint8Array): Promise { + if (!this.accountWallet) { + this.accountWallet = await HardhatZksyncSigner._getProperSigner( + (this.provider as HardhatZksyncEthersProvider).hre, + this._address, + ); + } + + if (!this.accountWallet) { + throw new Error(`Account ${this._address} is not managed by the node you are connected to.`); + } + + return this.accountWallet.signMessage(message); + } + + public async _signTypedData( + domain: ethers.TypedDataDomain, + types: Record, + value: Record, + ): Promise { + if (!this.accountWallet) { + this.accountWallet = await HardhatZksyncSigner._getProperSigner( + (this.provider as HardhatZksyncEthersProvider).hre, + this._address, + ); + } + + if (!this.accountWallet) { + throw new Error(`Account ${this._address} is not managed by the node you are connected to.`); + } + + return this.accountWallet._signTypedData(domain, types, value); + } + + public async signTransaction(transaction: TransactionRequest): Promise { + if (!this.accountWallet) { + this.accountWallet = await HardhatZksyncSigner._getProperSigner( + (this.provider as HardhatZksyncEthersProvider).hre, + this._address, + ); + } + + if (!this.accountWallet) { + throw new Error(`Account ${this._address} is not managed by the node you are connected to.`); + } + const tx = await this._prepareTransaction(transaction); + return this.accountWallet.signTransaction(tx); + } + + private async _sendTransaction(transaction: TransactionRequest): Promise { + const tx = await this._prepareTransaction(transaction); + tx.customData = tx.customData || {}; + tx.customData.customSignature = await (this.accountWallet as EIP712Signer).sign(transaction); + + const txBytes = serialize(tx); + return await this.provider.sendTransaction(txBytes); + } + + private async _prepareTransaction(transaction: TransactionRequest): Promise { + if (!transaction.customData && !transaction.type) { + // use legacy txs by default + transaction.type = 0; + } + if (!transaction.customData && transaction.type !== EIP712_TX_TYPE) { + return (await super.populateTransaction(transaction)) as TransactionRequest; + } + + const address = await this.getAddress(); + transaction.from ??= address; + if (!isAddressEq(transaction.from, address)) { + throw new Error('Transaction `from` address mismatch!'); + } + transaction.type = EIP712_TX_TYPE; + transaction.value ??= 0; + transaction.data ??= '0x'; + transaction.nonce ??= await this.getNonce(); + transaction.customData = this._fillCustomData(transaction.customData ?? {}); + transaction.gasPrice ??= await this.provider.getGasPrice(); + transaction.gasLimit ??= await this.provider.estimateGas(transaction); + transaction.chainId ??= (await this.provider.getNetwork()).chainId; + + return transaction; + } + + private static async _getProperSigner( + hre: HardhatRuntimeEnvironment, + address: string, + ): Promise { + let signer: Wallet | HardhatZksyncEIP712Signer | undefined = await findWalletFromAddress(address, hre); + if (!signer && (await isImpersonatedSigner(hre.ethers.provider, address))) { + signer = new HardhatZksyncEIP712Signer( + new Wallet(richWallets[LOCAL_CHAIN_IDS_ENUM.ERA_NODE][0].privateKey), + hre.ethers.provider.getNetwork().then((n) => Number(n.chainId)), + ); + } + + return signer; + } +} diff --git a/packages/hardhat-zksync-ethers/src/type-extensions.ts b/packages/hardhat-zksync-ethers/src/type-extensions.ts new file mode 100644 index 000000000..4915a7e3e --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/type-extensions.ts @@ -0,0 +1,37 @@ +import * as zk from 'zksync-ethers'; + +import { HardhatEthersHelpers } from '@nomiclabs/hardhat-ethers/types'; +import { ethers } from 'ethers'; +import type { EthNetwork, HardhatZksyncEthersHelpers } from './types'; +import 'hardhat/types/runtime'; + +declare module 'hardhat/types/config' { + interface HttpNetworkUserConfig { + zksync?: boolean; + ethNetwork?: EthNetwork; + } + + interface HttpNetworkConfig { + zksync: boolean; + ethNetwork?: EthNetwork; + } + + interface HardhatNetworkUserConfig { + zksync?: boolean; + } + + interface HardhatNetworkConfig { + zksync: boolean; + } +} + +declare module 'hardhat/types/runtime' { + interface Network { + zksync: boolean; + } + + interface HardhatRuntimeEnvironment { + ethers: (typeof zk & typeof ethers) & (HardhatZksyncEthersHelpers & HardhatEthersHelpers); + zksyncEthers: typeof zk & HardhatZksyncEthersHelpers; + } +} diff --git a/packages/hardhat-zksync-ethers/src/types.ts b/packages/hardhat-zksync-ethers/src/types.ts new file mode 100644 index 000000000..5d9f61cf4 --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/types.ts @@ -0,0 +1,111 @@ +import type * as ethers from 'ethers'; +import type { Artifact } from 'hardhat/types'; +import { Contract, ContractFactory, Provider, Wallet } from 'zksync-ethers'; +import { Address, DeploymentType } from 'zksync-ethers/build/types'; +import { HardhatZksyncSigner } from './signers/hardhat-zksync-signer'; + +export type EthNetwork = string; + +export interface FactoryDeps { + // A mapping from the contract hash to the contract bytecode. + [contractHash: string]: string; +} + +export interface ZkSyncArtifact extends Artifact { + // List of factory dependencies of a contract. + factoryDeps: FactoryDeps; + // Mapping from the bytecode to the zkEVM assembly (used for tracing). + sourceMapping: string; +} + +export interface ZkFactoryOptions { + wallet?: Wallet; + signer?: HardhatZksyncSigner; +} + +export type HardhatZksyncSignerOrWallet = Wallet | HardhatZksyncSigner; +export type HardhatZksyncSignerOrWalletOrFactoryOptions = HardhatZksyncSignerOrWallet | ZkFactoryOptions; + +export type GetContractFactoryArtifactName = ( + name: string, + walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, +) => Promise; + +export type GetContractFactoryAbiBytecode = ( + abi: any[], + bytecode: ethers.BytesLike, + walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, +) => Promise; + +export type GetContractFactoryArtifact = ( + artifact: ZkSyncArtifact, + walletOrSigner?: HardhatZksyncSignerOrWalletOrFactoryOptions, + deploymentType?: DeploymentType, +) => Promise; + +export type GetContractFactory = + | GetContractFactoryArtifactName + | GetContractFactoryAbiBytecode + | GetContractFactoryArtifact; + +export declare function getContractFactory( + ...args: Parameters +): Promise; + +export declare function getContractFactory( + ...args: Parameters +): Promise; + +export declare function getContractFactoryFromArtifact( + ...args: Parameters +): Promise; + +export type GetContractAtFromName = ( + name: string, + address: string | Address, + walletOrSigner?: HardhatZksyncSignerOrWallet, +) => Promise; + +export type GetContractAtFromAbi = ( + abi: any[], + address: string | Address, + walletOrSigner?: HardhatZksyncSignerOrWallet, +) => Promise; + +export type GetContractAtFromArtifact = ( + artifact: ZkSyncArtifact, + address: string | Address, + walletOrSigner?: HardhatZksyncSignerOrWallet, +) => Promise; + +export type GetContractAt = GetContractAtFromName | GetContractAtFromAbi | GetContractAtFromArtifact; + +export declare function getContractAt(...args: Parameters): Promise; + +export declare function getContractAt(...args: Parameters): Promise; + +export declare function getContractAtFromArtifact(...args: Parameters): Promise; + +export interface HardhatZksyncEthersHelpers { + providerL1: ethers.providers.Provider; + providerL2: Provider; + provider: Provider; + getSigners: () => Promise; + getSigner(address: string): Promise; + getWallets: () => Promise; + getWallet: (privateKeyOrIndex?: string | number) => Promise; + getContractFactory: typeof getContractFactory; + getContractAt: typeof getContractAt; + getImpersonatedSigner: (address: string) => Promise; + extractFactoryDeps: (artifact: ZkSyncArtifact) => Promise; + loadArtifact: (name: string) => Promise; + deployContract: ( + artifact: ZkSyncArtifact | string, + constructorArguments: any[], + walletOrSigner?: HardhatZksyncSignerOrWallet, + overrides?: ethers.Overrides, + additionalFactoryDeps?: ethers.BytesLike[], + ) => Promise; +} diff --git a/packages/hardhat-zksync-ethers/src/utils.ts b/packages/hardhat-zksync-ethers/src/utils.ts new file mode 100644 index 000000000..ab9362dac --- /dev/null +++ b/packages/hardhat-zksync-ethers/src/utils.ts @@ -0,0 +1,268 @@ +import { + HardhatNetworkAccountsConfig, + HardhatNetworkHDAccountsConfig, + HardhatRuntimeEnvironment, + HttpNetworkAccountsConfig, + HttpNetworkConfig, + NetworkConfig, +} from 'hardhat/types'; +import { Provider, Signer, Wallet } from 'zksync-ethers'; +import { ethers } from 'ethers'; +import { isAddressEq } from 'zksync-ethers/build/utils'; +import { + HardhatZksyncSignerOrWallet, + HardhatZksyncSignerOrWalletOrFactoryOptions, + ZkFactoryOptions, + ZkSyncArtifact, +} from './types'; +import { + ETH_DEFAULT_NETWORK_RPC_URL, + LOCAL_CHAIN_IDS, + LOCAL_CHAIN_IDS_ENUM, + LOCAL_CHAINS_WITH_IMPERSONATION, + SUPPORTED_L1_TESTNETS, +} from './constants'; +import { richWallets } from './rich-wallets'; +import { ZkSyncEthersPluginError } from './errors'; +import { HardhatZksyncEthersProvider } from './hardhat-zksync-provider'; +import { HardhatZksyncSigner } from './signers/hardhat-zksync-signer'; +import { getWallets } from './helpers'; + +export function isHardhatNetworkHDAccountsConfig(object: any): object is HardhatNetworkHDAccountsConfig { + return 'mnemonic' in object; +} + +export function isHardhatNetworkAccountsConfigStrings(object: any): object is string[] { + return typeof object[0] === 'string'; +} + +export function isString(object: any): object is string { + return typeof object === 'string'; +} + +export function isNumber(object: any): object is number { + return typeof object === 'number'; +} + +export async function getWalletsFromAccount( + hre: HardhatRuntimeEnvironment, + accounts: HardhatNetworkAccountsConfig | HttpNetworkAccountsConfig, +): Promise { + if (!accounts || accounts === 'remote') { + return await getRichWalletsIfPossible(hre); + } + + if (isHardhatNetworkAccountsConfigStrings(accounts)) { + const accountPrivateKeys = accounts as string[]; + + const wallets = accountPrivateKeys.map((accountPrivateKey) => + new Wallet(accountPrivateKey, hre.ethers.provider).connectToL1(hre.ethers.providerL1), + ); + return wallets; + } + + if (isHardhatNetworkHDAccountsConfig(accounts)) { + const account = accounts as HardhatNetworkHDAccountsConfig; + + const wallet = Wallet.fromMnemonic(account.mnemonic) + .connect(hre.ethers.provider) + .connectToL1(hre.ethers.providerL1); + return [wallet]; + } + + return []; +} + +export function isFactoryOptions( + walletOrOptions?: (Wallet | Signer) | ZkFactoryOptions, +): walletOrOptions is ZkFactoryOptions { + if (walletOrOptions === undefined || 'provider' in walletOrOptions) { + return false; + } + + return true; +} + +export function isArtifact(artifact: any): artifact is ZkSyncArtifact { + const { + contractName, + sourceName, + abi, + bytecode, + deployedBytecode, + linkReferences, + deployedLinkReferences, + factoryDeps, + } = artifact; + + return ( + typeof contractName === 'string' && + typeof sourceName === 'string' && + Array.isArray(abi) && + typeof bytecode === 'string' && + typeof deployedBytecode === 'string' && + linkReferences !== undefined && + deployedLinkReferences !== undefined && + factoryDeps !== undefined + ); +} + +export function createProviders(hre: HardhatRuntimeEnvironment): { + ethWeb3Provider: ethers.providers.Provider; + zkWeb3Provider: HardhatZksyncEthersProvider; +} { + const network = hre.network; + const networks = hre.config.networks; + + const networkName = network.name; + + if (!network.zksync) { + throw new ZkSyncEthersPluginError( + `Only deploying to ZKsync network is supported.\nNetwork '${networkName}' in 'hardhat.config' needs to have 'zksync' flag set to 'true'.`, + ); + } + + if (networkName === 'hardhat') { + return { + ethWeb3Provider: _createDefaultEthProvider(), + zkWeb3Provider: _createDefaultZkProvider(hre), + }; + } + + const networkConfig = network.config; + + if (!isHttpNetworkConfig(networkConfig)) { + throw new ZkSyncEthersPluginError( + `Only deploying to ZKsync network is supported.\nNetwork '${networkName}' in 'hardhat.config' needs to have 'url' specified.`, + ); + } + + if (networkConfig.ethNetwork === undefined) { + throw new ZkSyncEthersPluginError( + `Only deploying to ZKsync network is supported.\nNetwork '${networkName}' in 'hardhat.config' needs to have 'ethNetwork' (layer 1) specified.`, + ); + } + + let ethWeb3Provider; + const ethNetwork = networkConfig.ethNetwork; + + if (SUPPORTED_L1_TESTNETS.includes(ethNetwork)) { + ethWeb3Provider = + ethNetwork in networks && isHttpNetworkConfig(networks[ethNetwork]) + ? new ethers.providers.JsonRpcProvider((networks[ethNetwork] as HttpNetworkConfig).url) + : ethers.getDefaultProvider(ethNetwork); + } else { + if (ethNetwork === 'localhost' || ethNetwork === '') { + ethWeb3Provider = _createDefaultEthProvider(); + } else if (isValidEthNetworkURL(ethNetwork)) { + ethWeb3Provider = new ethers.providers.JsonRpcProvider(ethNetwork); + } else { + ethWeb3Provider = + ethNetwork in networks && isHttpNetworkConfig(networks[ethNetwork]) + ? new ethers.providers.JsonRpcProvider((networks[ethNetwork] as HttpNetworkConfig).url) + : ethers.getDefaultProvider(ethNetwork); + } + } + + const zkWeb3Provider = new HardhatZksyncEthersProvider(hre, (network.config as HttpNetworkConfig).url); + + return { ethWeb3Provider, zkWeb3Provider }; +} + +export async function findWalletFromAddress( + address: string, + hre?: HardhatRuntimeEnvironment, + wallets?: Wallet[], +): Promise { + if (!hre) { + throw new Error('Hardhat runtime environment is required to find wallet from address'); + } + + if (!wallets) { + wallets = await getWallets(hre); + } + return wallets.find((w) => isAddressEq(w.address, address)); +} + +export async function getSignerAccounts(hre: HardhatRuntimeEnvironment): Promise { + const accounts: [] = await hre.ethers.provider.send('eth_accounts', []); + + if (!accounts || accounts.length === 0) { + const wallets = await getWallets(hre); + return wallets.map((w) => w.address); + } + + const allWallets = await getWallets(hre); + + return accounts.filter((account: string) => allWallets.some((wallet) => isAddressEq(wallet.address, account))); +} + +export async function getRichWalletsIfPossible(hre: HardhatRuntimeEnvironment): Promise { + const chainId = await hre.ethers.providerL2.send('eth_chainId', []); + if (LOCAL_CHAIN_IDS.includes(chainId)) { + const chainIdEnum = chainId as LOCAL_CHAIN_IDS_ENUM; + + return richWallets[chainIdEnum].map((wallet) => + new Wallet(wallet.privateKey, hre.ethers.provider).connectToL1(hre.ethers.providerL1), + ); + } + return []; +} + +function _createDefaultEthProvider(): ethers.providers.Provider { + return new ethers.providers.JsonRpcProvider(ETH_DEFAULT_NETWORK_RPC_URL); +} + +function _createDefaultZkProvider(hre: HardhatRuntimeEnvironment): HardhatZksyncEthersProvider { + return new HardhatZksyncEthersProvider(hre); +} + +export function getSignerOrWallet( + signerWalletOrFactoryOptions?: HardhatZksyncSignerOrWalletOrFactoryOptions, +): HardhatZksyncSignerOrWallet | undefined { + if (signerWalletOrFactoryOptions === undefined) { + return undefined; + } + + if (isFactoryOptions(signerWalletOrFactoryOptions)) { + if (signerWalletOrFactoryOptions.wallet) { + return signerWalletOrFactoryOptions.wallet as Wallet; + } else if (signerWalletOrFactoryOptions.signer) { + return signerWalletOrFactoryOptions.signer as HardhatZksyncSigner; + } + + return undefined; + } + + return signerWalletOrFactoryOptions as HardhatZksyncSignerOrWallet; +} + +export function isHttpNetworkConfig(networkConfig: NetworkConfig): networkConfig is HttpNetworkConfig { + return 'url' in networkConfig; +} + +export function isValidEthNetworkURL(string: string) { + try { + new URL(string); + return true; + } catch (_) { + return false; + } +} + +export async function isImpersonatedSigner(provider: Provider, address: string): Promise { + const chainId = await provider.send('eth_chainId', []); + + if (!LOCAL_CHAINS_WITH_IMPERSONATION.includes(chainId)) { + return false; + } + + const result = await provider.send('hardhat_stopImpersonatingAccount', [address]); + + if (!result) { + return false; + } + + await provider.send('hardhat_impersonateAccount', [address]); + return true; +} diff --git a/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/contracts/Greeter.sol b/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/contracts/Greeter.sol new file mode 100644 index 000000000..88f89e0dc --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/contracts/Greeter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +contract Greeter { + + string greeting; + constructor() { + greeting = "Hello, World!"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/contracts/IGreeter.sol b/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/contracts/IGreeter.sol new file mode 100644 index 000000000..e14937886 --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/contracts/IGreeter.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IGreeter { + function greet() external view returns (string memory); +} \ No newline at end of file diff --git a/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/hardhat.config.ts b/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/hardhat.config.ts new file mode 100644 index 000000000..cd0bb57d1 --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/fixture-projects/simple-accounts/hardhat.config.ts @@ -0,0 +1,49 @@ +import '@matterlabs/hardhat-zksync-solc'; +import '@matterlabs/hardhat-zksync-deploy'; +import '../../../src/index'; + +import { HardhatUserConfig } from 'hardhat/types'; + +const config: HardhatUserConfig = { + zksolc: { + version: '1.3.14', + compilerSource: 'binary', + settings: {}, + }, + networks: { + hardhat: { + zksync: true, + }, + zkSyncNetworkAccounts: { + allowUnlimitedContractSize: true, + url: 'http://0.0.0.0:3050', + accounts: [ + '0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e', + '0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3', + ], + ethNetwork: 'http://0.0.0.0:8545', + zksync: true, + }, + zkSyncNetworkMenmonic: { + allowUnlimitedContractSize: true, + url: 'http://0.0.0.0:3050', + ethNetwork: 'http://0.0.0.0:8545', + accounts: { + mnemonic: 'stuff slice staff easily soup parent arm payment cotton trade scatter struggle', + }, + zksync: true, + }, + zkSyncNetworkEmptyAccounts: { + allowUnlimitedContractSize: true, + url: 'http://0.0.0.0:3050', + ethNetwork: 'http://0.0.0.0:8545', + accounts: [], + zksync: true, + }, + }, + solidity: { + version: '0.8.9', + }, +}; + +export default config; diff --git a/packages/hardhat-zksync-ethers/test/fixture-projects/simple/contracts/Greeter.sol b/packages/hardhat-zksync-ethers/test/fixture-projects/simple/contracts/Greeter.sol new file mode 100644 index 000000000..88f89e0dc --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/fixture-projects/simple/contracts/Greeter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +contract Greeter { + + string greeting; + constructor() { + greeting = "Hello, World!"; + } + + function greet() public view returns (string memory) { + return greeting; + } + +} diff --git a/packages/hardhat-zksync-ethers/test/fixture-projects/simple/contracts/IGreeter.sol b/packages/hardhat-zksync-ethers/test/fixture-projects/simple/contracts/IGreeter.sol new file mode 100644 index 000000000..e14937886 --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/fixture-projects/simple/contracts/IGreeter.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.9; + +interface IGreeter { + function greet() external view returns (string memory); +} \ No newline at end of file diff --git a/packages/hardhat-zksync-ethers/test/fixture-projects/simple/hardhat.config.ts b/packages/hardhat-zksync-ethers/test/fixture-projects/simple/hardhat.config.ts new file mode 100644 index 000000000..a003effa3 --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/fixture-projects/simple/hardhat.config.ts @@ -0,0 +1,35 @@ +import '@matterlabs/hardhat-zksync-solc'; +import '@matterlabs/hardhat-zksync-deploy'; +import '../../../src/index'; + +import { HardhatUserConfig } from 'hardhat/types'; + +const config: HardhatUserConfig = { + zksolc: { + version: '1.3.14', + compilerSource: 'binary', + settings: {}, + }, + networks: { + hardhat: { + zksync: true, + }, + zkSyncNetwork: { + allowUnlimitedContractSize: true, + url: 'http://0.0.0.0:3050', + ethNetwork: 'http://0.0.0.0:8545', + zksync: true, + }, + zkSyncTestnet: { + allowUnlimitedContractSize: true, + url: 'https://sepolia.era.zksync.dev', + ethNetwork: 'https://sepolia.infura.io/v3/1d9d3e9f5c0b4b0e8b2e1b2b8b0b0b0b', + zksync: true, + }, + }, + solidity: { + version: '0.8.9', + }, +}; + +export default config; diff --git a/packages/hardhat-zksync-ethers/test/helpers.ts b/packages/hardhat-zksync-ethers/test/helpers.ts new file mode 100644 index 000000000..0c1025670 --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/helpers.ts @@ -0,0 +1,23 @@ +import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names'; +import { resetHardhatContext } from 'hardhat/plugins-testing'; +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import path from 'path'; + +declare module 'mocha' { + interface Context { + env: HardhatRuntimeEnvironment; + } +} + +export function useEnvironment(fixtureProjectName: string, networkName = 'zkSyncNetwork') { + beforeEach('Loading hardhat environment', async function () { + process.chdir(path.join(__dirname, 'fixture-projects', fixtureProjectName)); + process.env.HARDHAT_NETWORK = networkName; + this.env = require('hardhat'); + await this.env.run(TASK_COMPILE); + }); + + afterEach('Resetting hardhat', function () { + resetHardhatContext(); + }); +} diff --git a/packages/hardhat-zksync-ethers/test/tests.ts b/packages/hardhat-zksync-ethers/test/tests.ts new file mode 100644 index 000000000..280b06f0c --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/tests.ts @@ -0,0 +1,626 @@ +import { assert } from 'chai'; +import { Contract, Wallet } from 'zksync-ethers'; +import { BigNumber } from 'ethers'; +import { richWallets } from '../src/rich-wallets'; +import { HardhatZksyncSigner } from '../src'; +import { LOCAL_CHAIN_IDS_ENUM } from '../src/constants'; +import { useEnvironment } from './helpers'; +import '../src/type-extensions'; + +describe('Plugin tests', async function () { + describe('successful-compilation artifact', async function () { + useEnvironment('simple'); + + describe('HRE extensions', function () { + it('should extend hardhat runtime environment', async function () { + assert.isDefined(this.env.ethers); + assert.containsAllKeys(this.env.ethers, [ + 'provider', + 'providerL1', + 'providerL2', + 'provider', + 'getSigners', + 'getSigner', + 'getWallet', + 'getWallets', + 'getImpersonatedSigner', + 'getContractFactory', + 'getContractAt', + 'extractFactoryDeps', + 'loadArtifact', + 'deployContract', + ]); + + assert.isDefined(this.env.zksyncEthers); + assert.containsAllKeys(this.env.zksyncEthers, [ + 'provider', + 'providerL1', + 'providerL2', + 'provider', + 'getSigners', + 'getSigner', + 'getWallet', + 'getWallets', + 'getImpersonatedSigner', + 'getContractFactory', + 'getContractAt', + 'extractFactoryDeps', + 'loadArtifact', + 'deployContract', + ]); + }); + }); + + describe('Provider L2', function () { + it('the provider should handle requests', async function () { + const gasPrice = await this.env.ethers.providerL2.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('should get the gas price', async function () { + const feeData = await this.env.ethers.providerL2.getFeeData(); + + assert.isNotNull(feeData.gasPrice); + }); + }); + + describe('Provider', function () { + it('the provider should handle requests', async function () { + const gasPrice = await this.env.ethers.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('should get the gas price', async function () { + const feeData = await this.env.ethers.provider.getFeeData(); + + assert.isNotNull(feeData.gasPrice); + }); + }); + + describe('Provider L1', function () { + it('should return fee data', async function () { + const feeData = await this.env.ethers.providerL1.getFeeData(); + + assert.instanceOf(feeData.gasPrice, BigNumber); + assert.isNotNull(feeData.gasPrice); + }); + }); + + describe('getImpersonatedSigner', function () { + it.skip('should return the working impersonated signer', async function () { + const address = `0x${'ff'.repeat(20)}`; + const impersonatedSigner = await this.env.ethers.getImpersonatedSigner(address); + + assert.strictEqual(await impersonatedSigner.getAddress(), '0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF'); + }); + }); + + describe('wallet', function () { + it('get default wallet', async function () { + const wallet = await this.env.ethers.getWallet(); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'); + assert.isDefined(wallet._providerL1); + assert.isDefined(wallet._providerL2); + }); + it('get specific wallet', async function () { + const wallet = await this.env.ethers.getWallet( + richWallets[LOCAL_CHAIN_IDS_ENUM.LOCAL_SETUP][6].privateKey, + ); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + assert.isDefined(wallet._providerL1); + assert.isDefined(wallet._providerL2); + }); + it('should send a transaction', async function () { + const wallet = await this.env.ethers.getWallet(); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const response = await wallet.sendTransaction(tx); + + const receipt = await response.wait(); + + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + assert.strictEqual(receipt.status, 1); + }); + it('should deploy with wallet', async function () { + const artifact = await this.env.ethers.loadArtifact('Greeter'); + const contract: Contract = await this.env.ethers.deployContract(artifact, []); + + assert.isDefined(contract); + assert.equal(contract.address.length, 42); + }); + it('should allow to use the call method', async function () { + const wallet = await this.env.ethers.getWallet(); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const result = await wallet.call(tx); + + assert.isString(result); + }); + it('should populate a transaction', async function () { + const wallet = await this.env.ethers.getWallet(); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const populatedTransaction = await wallet.populateTransaction(tx); + + assert.strictEqual(populatedTransaction.from, wallet.address); + assert.strictEqual(populatedTransaction.type, 113); + assert.strictEqual(populatedTransaction.to, '0x0000000000000000000000000000000000008006'); + }); + it('should allow to use the estimateGas method', async function () { + const [wallet] = await this.env.ethers.getWallets(); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const result = await wallet.estimateGas(tx); + + assert.isTrue(result.toBigInt() > 0); + }); + it.skip('should return the balance of the account', async function () { + // we use the second signer because the first one is used in previous tests + const [, secWallet] = await this.env.ethers.getWallets(); + + assert.strictEqual( + (await this.env.ethers.providerL2.getBalance(secWallet.address)).toBigInt(), + 1000000000000000000000000000000n, + ); + }); + it('should return the transaction count of the account', async function () { + // we use the second signer because the first one is used in previous tests + const [, secWallet] = await this.env.ethers.getWallets(); + + assert.strictEqual(await this.env.ethers.providerL2.getTransactionCount(secWallet.address), 0); + }); + }); + + describe('signer', function () { + it('get all signers', async function () { + const signers = await this.env.ethers.getSigners(); + assert.equal(signers.length, 10); + }); + it('get specific signer', async function () { + const signer = await this.env.ethers.getSigner('0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + + assert.isDefined(signer); + assert.equal((await signer.getAddress()).length, 42); + assert.equal(await signer.getAddress(), '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + assert.isDefined(signer._providerL2); + }); + it('should send a transaction', async function () { + const signer = await this.env.ethers.getSigner('0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const response = await signer.sendTransaction(tx); + + const receipt = await response.wait(); + + if (receipt === null) { + assert.fail("receipt shoudn't be null"); + } + assert.strictEqual(receipt.status, 1); + }); + it('should deploy with default signer', async function () { + const artifact = await this.env.ethers.loadArtifact('Greeter'); + const contract: Contract = await this.env.ethers.deployContract(artifact, []); + + assert.isDefined(contract); + assert.equal(contract.address.length, 42); + assert.equal( + await (contract.signer as HardhatZksyncSigner).getAddress(), + '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + ); + }); + it('should deploy with provided signer', async function () { + const signer = await this.env.ethers.getSigner('0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + const contract: Contract = await this.env.ethers.deployContract('Greeter', [], signer); + + assert.isDefined(contract); + assert.equal(contract.address.length, 42); + assert.equal( + await (contract.signer as HardhatZksyncSigner).getAddress(), + '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA', + ); + }); + it('should allow to use the call method', async function () { + const signer = await this.env.ethers.getSigner('0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const result = await signer.call(tx); + + assert.isString(result); + }); + it('should populate a transaction', async function () { + const signer = await this.env.ethers.getSigner('0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const populatedTransaction = await signer.populateTransaction(tx); + + assert.strictEqual(populatedTransaction.from, await signer.getAddress()); + assert.strictEqual(populatedTransaction.type, 113); + assert.strictEqual(populatedTransaction.to, '0x0000000000000000000000000000000000008006'); + }); + it('should allow to use the estimateGas method', async function () { + const [, signer] = await this.env.ethers.getSigners(); + + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + const tx = Greeter.getDeployTransaction(); + + const result = await signer.estimateGas(tx); + + assert.isTrue(result.toBigInt() > 0n); + }); + it.skip('should return the balance of the account', async function () { + // we use the second signer because the first one is used in previous tests + const [, signer] = await this.env.ethers.getSigners(); + + assert.strictEqual( + (await this.env.ethers.providerL2.getBalance(await signer.getAddress())).toBigInt(), + 1000000000000000000000000000000n, + ); + }); + it('should return the transaction count of the account', async function () { + // we use the second signer because the first one is used in previous tests + const [, signer] = await this.env.ethers.getSigners(); + + assert.strictEqual(await this.env.ethers.provider.getTransactionCount(await signer.getAddress()), 0); + }); + }); + + describe('getContractFactory', function () { + it('should return a contract factory', async function () { + const contract = await this.env.ethers.getContractFactory('Greeter'); + + assert.isNotNull(contract.interface.getFunction('greet')); + + // non-existent functions should be null + try { + contract.interface.getFunction('doesntExist'); + } catch (err: any) { + assert.include( + err.message, + 'no matching function (argument="name", value="doesntExist", code=INVALID_ARGUMENT', + ); + } + }); + it('should return a contract factory with provided signer', async function () { + const signer = await this.env.ethers.getSigner('0x0D43eB5B8a47bA8900d84AA36656c92024e9772e'); + const contract = await this.env.ethers.getContractFactory('Greeter', signer); + + assert.isNotNull(contract.interface.getFunction('greet')); + assert.strictEqual( + await (contract.signer as HardhatZksyncSigner).getAddress(), + await signer.getAddress(), + ); + + // non-existent functions should be null + try { + contract.interface.getFunction('doesntExist'); + } catch (err: any) { + assert.include( + err.message, + 'no matching function (argument="name", value="doesntExist", code=INVALID_ARGUMENT', + ); + } + }); + it('should fail to return a contract factory for an interface', async function () { + try { + await this.env.ethers.getContractFactory('IGreeter'); + } catch (err: any) { + assert.equal( + err.message, + 'You are trying to create a contract factory for the contract IGreeter, which is abstract and can\'t be deployed.\nIf you want to call a contract using IGreeter as its interface use the "getContractAt" function instead.', + ); + } + }); + it('should return a contract factory', async function () { + const artifact = await this.env.ethers.loadArtifact('Greeter'); + const contract = await this.env.ethers.getContractFactory(artifact.abi, artifact.bytecode); + + assert.isNotNull(contract.interface.getFunction('greet')); + }); + it('Should be able to send txs and make calls', async function () { + const artifact = await this.env.ethers.loadArtifact('Greeter'); + + const Greeter = await this.env.ethers.getContractFactory(artifact.abi, artifact.bytecode); + + const greeter = await Greeter.deploy(); + + assert.strictEqual(await greeter.greet(), 'Hello, World!'); + }); + }); + + describe('getContractAt', function () { + let deployedGreeter: Contract; + + beforeEach(async function () { + const Greeter = await this.env.ethers.getContractFactory('Greeter'); + deployedGreeter = await Greeter.deploy(); + }); + it('Should return an instance of a contract', async function () { + const signer = await this.env.ethers.getSigner('0x0D43eB5B8a47bA8900d84AA36656c92024e9772e'); + const contract = await this.env.ethers.getContractAt('Greeter', deployedGreeter.address, signer); + + assert.exists(contract.greet); + + assert.strictEqual( + await (contract.signer as HardhatZksyncSigner).getAddress(), + await signer.getAddress(), + ); + }); + it('Should return an instance of an interface', async function () { + const wallet = await this.env.ethers.getWallet(); + const contract = await this.env.ethers.getContractAt('IGreeter', deployedGreeter.address, wallet); + + assert.isNotNull(contract.interface.getFunction('greet')); + + assert.strictEqual(await (contract.signer as Wallet).getAddress(), await wallet.getAddress()); + }); + it('Should be able to send txs and make calls', async function () { + const greeter = await this.env.ethers.getContractAt('Greeter', deployedGreeter.address); + + assert.strictEqual(await greeter.greet(), 'Hello, World!'); + }); + it('Should return an instance of a contract from artifact', async function () { + const artifact = await this.env.ethers.loadArtifact('Greeter'); + const contract = await this.env.ethers.getContractAtFromArtifact(artifact, deployedGreeter.address); + + assert.exists(contract.greet); + assert.strictEqual(await contract.greet(), 'Hello, World!'); + }); + it('Should return an instance of a contract from abi', async function () { + const artifact = await this.env.ethers.loadArtifact('Greeter'); + const contract = await this.env.ethers.getContractAt(artifact.abi, deployedGreeter.address); + + assert.exists(contract.greet); + assert.strictEqual(await contract.greet(), 'Hello, World!'); + }); + }); + + describe('zksyncEthers legacy extension', function () { + describe('Provider L2', function () { + it('the provider should handle requests', async function () { + const gasPrice = await this.env.zksyncEthers.providerL2.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('should get the gas price', async function () { + const feeData = await this.env.zksyncEthers.providerL2.getFeeData(); + + assert.isNotNull(feeData.gasPrice); + }); + }); + + describe('Provider', function () { + it('the provider should handle requests', async function () { + const gasPrice = await this.env.zksyncEthers.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('should get the gas price', async function () { + const feeData = await this.env.zksyncEthers.provider.getFeeData(); + + assert.isNotNull(feeData.gasPrice); + }); + }); + + describe('Provider L1', function () { + it('should return fee data', async function () { + const feeData = await this.env.zksyncEthers.providerL1.getFeeData(); + + assert.instanceOf(feeData.gasPrice, BigNumber); + assert.isNotNull(feeData.gasPrice); + }); + }); + + describe('getContractFactory', function () { + it('should return a contract factory', async function () { + const contract = await this.env.zksyncEthers.getContractFactory('Greeter'); + + assert.isNotNull(contract.interface.getFunction('greet')); + + // non-existent functions should be null + try { + contract.interface.getFunction('doesntExist'); + } catch (err: any) { + assert.include( + err.message, + 'no matching function (argument="name", value="doesntExist", code=INVALID_ARGUMENT', + ); + } + }); + }); + }); + }); + + describe('wallets with accounts', async function () { + describe('wallets with accounts strings', async function () { + useEnvironment('simple-accounts', 'zkSyncNetworkAccounts'); + it('get default wallet', async function () { + const wallet = await this.env.ethers.getWallet(); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0x0D43eB5B8a47bA8900d84AA36656c92024e9772e'); + + const gasPrice = await wallet.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('get valid second wallet', async function () { + const wallet = await this.env.ethers.getWallet(1); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0xa61464658AfeAf65CccaaFD3a512b69A83B77618'); + + const gasPrice = await wallet.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('get invalid third wallet', async function () { + try { + const _ = await this.env.ethers.getWallet(3); + } catch (err: any) { + assert.equal(err.message, 'Account private key with specified index is not found'); + } + }); + it('get wallet with private key', async function () { + const wallet = await this.env.ethers.getWallet( + richWallets[LOCAL_CHAIN_IDS_ENUM.LOCAL_SETUP][6].privateKey, + ); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + + const gasPrice = await wallet.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + }); + + describe('signers with accounts', async function () { + describe('signers with accounts strings', async function () { + useEnvironment('simple-accounts', 'zkSyncNetworkAccounts'); + it('get default signer', async function () { + const signer = (await this.env.ethers.getSigners())[0]; + + assert.isDefined(signer); + assert.equal((await signer.getAddress()).length, 42); + assert.equal(await signer.getAddress(), '0x0D43eB5B8a47bA8900d84AA36656c92024e9772e'); + + const gasPrice = await signer.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('get valid second signer', async function () { + const signer = await this.env.ethers.getSigner('0xa61464658AfeAf65CccaaFD3a512b69A83B77618'); + + assert.isDefined(signer); + + const gasPrice = await signer.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + + const response = await signer.sendTransaction({ + to: '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049', + value: 0, + data: '0x', + }); + + assert.equal(response.hash.length, 66); + assert.equal(response.from, '0xa61464658AfeAf65CccaaFD3a512b69A83B77618'); + }); + it('get invalid third signer', async function () { + const signer = (await this.env.ethers.getSigners())[15]; + + assert.isUndefined(signer); + }); + + it('get invalid signer that doesnt exist in the accounts', async function () { + const signer = await this.env.ethers.getSigner('0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'); + + assert.isDefined(signer); + + try { + await signer.sendTransaction({ + to: '0xa61464658AfeAf65CccaaFD3a512b69A83B77618', + value: 0, + data: '0x', + }); + } catch (err: any) { + assert.equal( + err.message, + 'Account 0x36615Cf349d7F6344891B1e7CA7C72883F5dc049 is not managed by the node you are connected to.', + ); + } + }); + }); + }); + + describe('wallets with accounts mnemonic', async function () { + useEnvironment('simple-accounts', 'zkSyncNetworkMenmonic'); + it('get default wallet from mnemonic', async function () { + const wallet = await this.env.ethers.getWallet(); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0x36615Cf349d7F6344891B1e7CA7C72883F5dc049'); + + const gasPrice = await wallet.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + it('get invalid second wallet with mnemonic', async function () { + try { + const _ = await this.env.ethers.getWallet(1); + } catch (err: any) { + assert.equal(err.message, 'Account private key with specified index is not found'); + } + }); + it('get wallet with private key and with mnemonic', async function () { + const wallet = await this.env.ethers.getWallet( + richWallets[LOCAL_CHAIN_IDS_ENUM.LOCAL_SETUP][6].privateKey, + ); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + + const gasPrice = await wallet.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + }); + describe('wallets with empty accounts', async function () { + useEnvironment('simple-accounts', 'zkSyncNetworkEmptyAccounts'); + it('get default wallet from empty accounts', async function () { + try { + const _ = await this.env.ethers.getWallet(); + } catch (err: any) { + assert.equal(err.message, 'Accounts are not configured for this network'); + } + }); + it('get invalid second wallet with empty accounts', async function () { + try { + const _ = await this.env.ethers.getWallet(1); + } catch (err: any) { + assert.equal(err.message, 'Account private key with specified index is not found'); + } + }); + it('get wallet with private key and with empty accounts', async function () { + const wallet = await this.env.ethers.getWallet( + richWallets[LOCAL_CHAIN_IDS_ENUM.LOCAL_SETUP][6].privateKey, + ); + + assert.isDefined(wallet); + assert.equal((await wallet.getAddress()).length, 42); + assert.equal(await wallet.getAddress(), '0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA'); + + const gasPrice = await wallet.provider.send('eth_gasPrice', []); + + assert.strictEqual('0x5f5e100', gasPrice); + }); + }); + }); +}); diff --git a/packages/hardhat-zksync-ethers/test/utils.ts b/packages/hardhat-zksync-ethers/test/utils.ts new file mode 100644 index 000000000..e2a400f8c --- /dev/null +++ b/packages/hardhat-zksync-ethers/test/utils.ts @@ -0,0 +1,2 @@ +export const greeterBytecode = + '0x0002000000000002000100000000000200010000000103550000006001100270000000600010019d0000008001000039000000400010043f0000000101200190000000330000c13d0000000001000031000000040110008c000000930000413d0000000101000367000000000101043b0000006601100197000000670110009c000000930000c13d0000000001000416000000000101004b000000930000c13d000000040100008a00000000011000310000006202000041000000000301004b000000000300001900000000030240190000006201100197000000000401004b000000000200a019000000620110009c00000000010300190000000001026019000000000101004b000000930000c13d000000000100041a000000010310019000000001021002700000007f0420018f00000000020460190000001f0420008c00000000040000190000000104002039000000010440018f000000000443004b0000009b0000613d000000640100004100000000001004350000002201000039000000040010043f00000065010000410000017c000104300000000001000416000000000101004b000000930000c13d00000000020000310000001f03200039000000200100008a000000000513016f000000400300043d0000000004350019000000000554004b00000000050000190000000105004039000000610640009c000000950000213d0000000105500190000000950000c13d000000400040043f0000001f0420018f00000001050003670000000506200272000000510000613d000000000700001900000005087002100000000009830019000000000885034f000000000808043b00000000008904350000000107700039000000000867004b000000490000413d000000000704004b000000600000613d0000000506600210000000000565034f00000000066300190000000304400210000000000706043300000000074701cf000000000747022f000000000505043b0000010004400089000000000545022f00000000044501cf000000000474019f00000000004604350000006204000041000000200520008c000000000500001900000000050440190000006206200197000000000706004b000000000400a019000000620660009c000000000405c019000000000404004b000000930000c13d0000000005030433000000610450009c000000930000213d000000000423001900000000023500190000001f032000390000006205000041000000000643004b0000000006000019000000000605801900000062033001970000006207400197000000000873004b0000000005008019000000000373013f000000620330009c00000000030600190000000003056019000000000303004b000000930000c13d0000000003020433000000610530009c000000950000213d0000003f05300039000000000515016f000000400100043d0000000005510019000000000615004b00000000060000190000000106004039000000610750009c000000950000213d0000000106600190000000950000c13d000000400050043f000000000031043500000020053000390000000006250019000000000446004b000000c10000a13d00000000010000190000017c00010430000000640100004100000000001004350000004101000039000000040010043f00000065010000410000017c00010430000000800020043f000000000303004b000000a30000c13d000001000200008a000000000121016f000000a00010043f0000004001000039000000b20000013d00000020010000390000000000000435000000000302004b000000b20000613d000000680100004100000000040000190000000003040019000000000401041a000000a005300039000000000045043500000001011000390000002004300039000000000524004b000000a90000413d0000004001300039017a01670000040f000000400100043d000100000001001d017a014e0000040f000000010400002900000000014100490000006002000041000000600310009c0000000001028019000000600340009c000000000204401900000040022002100000006001100210000000000121019f0000017b0001042e000000000403004b000000cf0000613d000000000400001900000020044000390000000006140019000000000724001900000000070704330000000000760435000000000634004b000000c40000413d000000000234004b000000cf0000a13d00000000021500190000000000020435017a00d50000040f00000020010000390000010000100443000001200000044300000063010000410000017b0001042e00040000000000020000000064010434000000690240009c000001400000813d000000000300041a000000010230019000000001053002700000007f0350018f00000000050360190000001f0350008c00000000030000190000000103002039000000010330018f000000000232004b000001460000c13d000300000001001d000000200150008c000400000004001d000001070000413d000100000005001d000200000006001d000000000000043500000060010000410000000002000414000000600320009c0000000001024019000000c0011002100000006a011001c70000801002000039017a01750000040f00000001022001900000014c0000613d00000004040000290000001f024000390000000502200270000000200340008c0000000002004019000000000301043b00000001010000290000001f01100039000000050110027000000000011300190000000002230019000000000312004b0000000206000029000001070000813d000000000002041b0000000102200039000000000312004b000001030000413d0000001f0140008c000001330000a13d000000000000043500000060010000410000000002000414000000600320009c0000000001024019000000c0011002100000006a011001c70000801002000039017a01750000040f00000001022001900000014c0000613d000000200200008a000000040600002900000000032601700000002002000039000000000101043b0000000307000029000001250000613d0000002002000039000000000400001900000000057200190000000005050433000000000051041b000000200220003900000001011000390000002004400039000000000534004b0000011d0000413d000000000363004b000001300000813d0000000303600210000000f80330018f000000010400008a000000000334022f000000000343013f00000000027200190000000002020433000000000232016f000000000021041b000000010160021000000001011001bf0000013e0000013d000000000104004b00000000010000190000013e0000613d0000000301400210000000010200008a000000000112022f000000000121013f0000000002060433000000000112016f0000000102400210000000000121019f000000000010041b000000000001042d000000640100004100000000001004350000004101000039000000040010043f00000065010000410000017c00010430000000640100004100000000001004350000002201000039000000040010043f00000065010000410000017c0001043000000000010000190000017c0001043000000020020000390000000003210436000000800200043d0000000000230435000000000302004b0000004001100039000001620000613d00000000030000190000000004310019000000a005300039000000000505043300000000005404350000002003300039000000000423004b000001560000413d000000000323004b000001620000a13d00000000032100190000000000030435000001620000013d0000001f02200039000000200300008a000000000232016f0000000001210019000000000001042d0000009f01100039000000200200008a000000000121016f0000006b021000410000006c0220009c0000016f0000a13d000000400010043f000000000001042d000000640100004100000000001004350000004101000039000000040010043f00000065010000410000017c0001043000000178002104230000000102000039000000000001042d0000000002000019000000000001042d0000017a000004320000017b0001042e0000017c0001043000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff000000000000000000000000000000000000000000000000ffffffffffffffff800000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000400000010000000000000000004e487b71000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024000000000000000000000000ffffffff00000000000000000000000000000000000000000000000000000000cfae321700000000000000000000000000000000000000000000000000000000290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e56300000000000000000000000000000000000000000000000100000000000000000200000000000000000000000000000000000020000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff000000000000007f00000000000000000000000000000000000000000000000000000000000000007c506d190dd8dbdaf3e39f8668bec711e025c23192672aa02d0735515e37cd02'; diff --git a/packages/hardhat-zksync-ethers/tsconfig.json b/packages/hardhat-zksync-ethers/tsconfig.json new file mode 100644 index 000000000..e6038e7fe --- /dev/null +++ b/packages/hardhat-zksync-ethers/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../config/typescript/tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "exclude": ["./dist", "./node_modules", "./test/**/*"] +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7483ee906..b5283ae35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -222,6 +222,67 @@ importers: specifier: ^5.3.0 version: 5.4.5 + examples/zksync-ethers-example: + dependencies: + '@matterlabs/hardhat-zksync-deploy': + specifier: workspace:^ + version: link:../../packages/hardhat-zksync-deploy + '@matterlabs/hardhat-zksync-ethers': + specifier: workspace:^ + version: link:../../packages/hardhat-zksync-ethers + '@matterlabs/hardhat-zksync-solc': + specifier: ^1.2.1 + version: 1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + chalk: + specifier: ^4.1.2 + version: 4.1.2 + ethers: + specifier: ~5.7.2 + version: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + hardhat: + specifier: ^2.14.0 + version: 2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10) + zksync-ethers: + specifier: ^5.8.0 + version: 5.8.0(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + devDependencies: + '@types/node': + specifier: ^18.11.17 + version: 18.19.36 + '@typescript-eslint/eslint-plugin': + specifier: ^7.12.0 + version: 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^7.12.0 + version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) + eslint: + specifier: ^8.56.0 + version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + eslint-plugin-import: + specifier: ^2.29.1 + version: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-no-only-tests: + specifier: ^3.1.0 + version: 3.1.0 + eslint-plugin-prettier: + specifier: ^5.0.1 + version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.0) + prettier: + specifier: ^3.3.0 + version: 3.3.0 + rimraf: + specifier: ^5.0.7 + version: 5.0.7 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.19.36)(typescript@5.4.5) + typescript: + specifier: ^5.3.0 + version: 5.4.5 + packages/hardhat-zksync: dependencies: '@matterlabs/hardhat-zksync-deploy': @@ -425,6 +486,97 @@ importers: specifier: ^5.8.0 version: 5.8.0(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + packages/hardhat-zksync-ethers: + dependencies: + '@matterlabs/hardhat-zksync-deploy': + specifier: workspace:^ + version: link:../hardhat-zksync-deploy + '@matterlabs/hardhat-zksync-solc': + specifier: ^1.2.1 + version: 1.2.1(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + '@nomiclabs/hardhat-ethers': + specifier: ^2.2.3 + version: 2.2.3(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10))(hardhat@2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10)) + chai: + specifier: ^4.3.4 + version: 4.4.1 + chalk: + specifier: ^4.1.2 + version: 4.1.2 + hardhat: + specifier: ^2.14.0 + version: 2.14.0(bufferutil@4.0.8)(ts-node@10.9.2(@types/node@18.19.36)(typescript@5.4.5))(typescript@5.4.5)(utf-8-validate@5.0.10) + devDependencies: + '@types/chai': + specifier: ^4.3.16 + version: 4.3.16 + '@types/chai-as-promised': + specifier: ^7.1.8 + version: 7.1.8 + '@types/lodash.isequal': + specifier: ^4.5.8 + version: 4.5.8 + '@types/mocha': + specifier: ^10.0.6 + version: 10.0.6 + '@types/node': + specifier: ^18.0.0 + version: 18.19.36 + '@types/sinon': + specifier: ^17.0.3 + version: 17.0.3 + '@typescript-eslint/eslint-plugin': + specifier: ^7.12.0 + version: 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: 7.12.0 + version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) + c8: + specifier: ^8.0.1 + version: 8.0.1 + eslint: + specifier: ^8.56.0 + version: 8.57.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + eslint-plugin-import: + specifier: ^2.29.1 + version: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-no-only-tests: + specifier: ^3.1.0 + version: 3.1.0 + eslint-plugin-prettier: + specifier: ^5.0.1 + version: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.3.0) + ethers: + specifier: ~5.7.2 + version: 5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + mocha: + specifier: ^10.4.0 + version: 10.4.0 + prettier: + specifier: ^3.3.0 + version: 3.3.0 + rimraf: + specifier: ^5.0.7 + version: 5.0.7 + rlp: + specifier: 3.0.0 + version: 3.0.0 + sinon: + specifier: ^18.0.0 + version: 18.0.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@18.19.36)(typescript@5.4.5) + typescript: + specifier: ^5.3.0 + version: 5.4.5 + zksync-ethers: + specifier: ^5.8.0 + version: 5.8.0(ethers@5.7.2(bufferutil@4.0.8)(utf-8-validate@5.0.10)) + packages/hardhat-zksync-upgradable: dependencies: '@ethersproject/abi': @@ -1015,6 +1167,9 @@ packages: '@types/bn.js@5.1.5': resolution: {integrity: sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==, tarball: https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz} + '@types/chai-as-promised@7.1.8': + resolution: {integrity: sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==, tarball: https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz} + '@types/chai@4.3.16': resolution: {integrity: sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==, tarball: https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz} @@ -1036,6 +1191,9 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==, tarball: https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz} + '@types/lodash.isequal@4.5.8': + resolution: {integrity: sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==, tarball: https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz} + '@types/lodash@4.17.5': resolution: {integrity: sha512-MBIOHVZqVqgfro1euRDWX7OO0fBVUUMrN6Pwm8LQsz8cWhEpihlvR70ENj3f40j58TNxZaWv2ndSkInykNBBJw==, tarball: https://registry.npmjs.org/@types/lodash/-/lodash-4.17.5.tgz} @@ -1422,6 +1580,11 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==, tarball: https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz} engines: {node: '>= 0.8'} + c8@8.0.1: + resolution: {integrity: sha512-EINpopxZNH1mETuI0DzRA4MZpAUH+IFiRhnmFD3vFr3vdrgxqi3VfE3KL0AIL+zDq8rC9bZqwM/VDmmoe04y7w==, tarball: https://registry.npmjs.org/c8/-/c8-8.0.1.tgz} + engines: {node: '>=12'} + hasBin: true + c8@9.1.0: resolution: {integrity: sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==, tarball: https://registry.npmjs.org/c8/-/c8-9.1.0.tgz} engines: {node: '>=14.14.0'} @@ -2062,6 +2225,10 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==, tarball: https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz} + foreground-child@2.0.0: + resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==, tarball: https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz} + engines: {node: '>=8.0.0'} + foreground-child@3.2.1: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==, tarball: https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz} engines: {node: '>=14'} @@ -3125,6 +3292,10 @@ packages: resolution: {integrity: sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==, tarball: https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz} hasBin: true + rlp@3.0.0: + resolution: {integrity: sha512-PD6U2PGk6Vq2spfgiWZdomLvRGDreBLxi5jv5M8EpRo3pU6VEm31KO+HFxE18Q3vgqfDrQ9pZA3FP95rkijNKw==, tarball: https://registry.npmjs.org/rlp/-/rlp-3.0.0.tgz} + hasBin: true + run-parallel-limit@1.1.0: resolution: {integrity: sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==, tarball: https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz} @@ -4722,6 +4893,10 @@ snapshots: dependencies: '@types/node': 18.19.36 + '@types/chai-as-promised@7.1.8': + dependencies: + '@types/chai': 4.3.16 + '@types/chai@4.3.16': {} '@types/debug@4.1.12': @@ -4746,6 +4921,10 @@ snapshots: dependencies: '@types/node': 18.19.36 + '@types/lodash.isequal@4.5.8': + dependencies: + '@types/lodash': 4.17.5 + '@types/lodash@4.17.5': {} '@types/lru-cache@5.1.1': {} @@ -5191,6 +5370,21 @@ snapshots: bytes@3.1.2: {} + c8@8.0.1: + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@istanbuljs/schema': 0.1.3 + find-up: 5.0.0 + foreground-child: 2.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.1.7 + rimraf: 3.0.2 + test-exclude: 6.0.0 + v8-to-istanbul: 9.2.0 + yargs: 17.7.2 + yargs-parser: 21.1.1 + c8@9.1.0: dependencies: '@bcoe/v8-coverage': 0.2.3 @@ -6043,6 +6237,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@2.0.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 3.0.7 + foreground-child@3.2.1: dependencies: cross-spawn: 7.0.3 @@ -7177,6 +7376,8 @@ snapshots: dependencies: bn.js: 5.2.1 + rlp@3.0.0: {} + run-parallel-limit@1.1.0: dependencies: queue-microtask: 1.2.3