diff --git a/packages/hardhat-zksync-network-helpers/.eslintrc.js b/packages/hardhat-zksync-network-helpers/.eslintrc.js new file mode 100644 index 000000000..56ca0b4bd --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/.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-network-helpers/.mocharc.json b/packages/hardhat-zksync-network-helpers/.mocharc.json new file mode 100644 index 000000000..d00ceb413 --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/.mocharc.json @@ -0,0 +1,5 @@ +{ + "require": "ts-node/register/files", + "ignore": ["test/fixture-projects/**/*"], + "timeout": 10000 +} diff --git a/packages/hardhat-zksync-network-helpers/.prettierignore b/packages/hardhat-zksync-network-helpers/.prettierignore new file mode 100644 index 000000000..37cbd4e3f --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/.prettierignore @@ -0,0 +1,5 @@ +/node_modules +/dist +/test/fixture-projects/**/artifacts +/test/fixture-projects/**/cache +CHANGELOG.md diff --git a/packages/hardhat-zksync-network-helpers/README.md b/packages/hardhat-zksync-network-helpers/README.md new file mode 100644 index 000000000..ac368d356 --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/README.md @@ -0,0 +1,5 @@ +# hardhat-zksync-network-helpers + +[Hardhat](https://hardhat.org/) plugin that is a plugin that provides set of utility functions to interact with [In-Memory Node] (https://era.zksync.io/docs/tools/testing/era-test-node.html). + +Check out the [documentation page](https://era.zksync.io/docs/tools/hardhat/) for more detailed explanation on how to use hardhat-zksync-network-helpers. \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/eslint-tsconfig.json b/packages/hardhat-zksync-network-helpers/eslint-tsconfig.json new file mode 100644 index 000000000..c86edc88a --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/eslint-tsconfig.json @@ -0,0 +1,6 @@ +{ + "exclude": [ + "./dist" + ], + "extends": "./tsconfig.json" +} \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/package.json b/packages/hardhat-zksync-network-helpers/package.json new file mode 100644 index 000000000..5f5acb283 --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/package.json @@ -0,0 +1,78 @@ +{ + "name": "@matterlabs/hardhat-zksync-network-helpers", + "version": "0.0.1-beta.1", + "description": "Hardhat plugin for interacting with In Memory Node", + "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": "yarn eslint", + "prettier:check": "yarn prettier --check", + "lint:fix": "yarn eslint --fix", + "fmt": "yarn prettier --write", + "eslint": "eslint 'src/**/*.ts' 'test/**/*.ts'", + "prettier": "prettier 'src/**/*.ts' 'test/**/*.ts'", + "test": "NODE_ENV=test mocha test/**/*.ts --no-timeout --exit", + "build": "tsc --build .", + "clean": "rimraf dist" + }, + "files": [ + "dist/", + "src/", + "LICENSE", + "README.md" + ], + "dependencies": { + "chalk": "5.3.0", + "ethereumjs-util": "^7.1.5" + }, + "devDependencies": { + "@types/chai": "^4.2.0", + "@types/chai-as-promised": "^7.1.3", + "@types/lodash.isequal": "^4.5.6", + "@types/mocha": ">=9.1.0", + "@types/node": "^16.0.0", + "@types/sinon": "^9.0.8", + "@typescript-eslint/eslint-plugin": "6.13.1", + "@typescript-eslint/parser": "6.13.1", + "c8": "^8.0.1", + "chai": "^4.2.0", + "eslint": "^8.54.0", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-import": "2.29.0", + "eslint-plugin-no-only-tests": "3.1.0", + "eslint-plugin-prettier": "5.0.1", + "ethers": "^6.7.1", + "hardhat": "^2.19.2", + "mocha": "^10.0.0", + "prettier": "3.1.0", + "rimraf": "^5.0.5", + "rlp": "3.0.0", + "sinon": "^9.0.0", + "ts-node": "^10.9.1", + "typescript": "^5.1.6", + "zksync-ethers": "^6.0.0" + }, + "peerDependencies": { + "ethers": "^6.7.1", + "zksync-ethers": "^6.0.0" + }, + "prettier": { + "tabWidth": 4, + "printWidth": 120, + "parser": "typescript", + "singleQuote": true, + "bracketSpacing": true + } +} diff --git a/packages/hardhat-zksync-network-helpers/src/helpers/impersonateAccount.ts b/packages/hardhat-zksync-network-helpers/src/helpers/impersonateAccount.ts new file mode 100644 index 000000000..407099e14 --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/src/helpers/impersonateAccount.ts @@ -0,0 +1,12 @@ +import { assertValidAddress, getProvider } from "../utils"; + +export async function impersonateAccount(address: string): Promise { + const provider = await getProvider(); + + assertValidAddress(address); + + await provider.send( + "hardhat_impersonateAccount", + [address], + ); + } \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/src/helpers/mine.ts b/packages/hardhat-zksync-network-helpers/src/helpers/mine.ts new file mode 100644 index 000000000..3c9061160 --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/src/helpers/mine.ts @@ -0,0 +1,13 @@ +import { NumberLike } from "../types"; +import { getProvider, toRpcQuantity } from "../utils"; + +export async function mine(blocks:NumberLike=1,options:{interval?:NumberLike}={}):Promise{ + const provider = await getProvider(); + const interval = options.interval ?? 1; + + const hexBlocks = toRpcQuantity(blocks); + const hexInterval = toRpcQuantity(interval); + + const result = await provider.send("hardhat_mine", [hexBlocks, hexInterval]); + return result; +} \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/src/helpers/setBalance.ts b/packages/hardhat-zksync-network-helpers/src/helpers/setBalance.ts new file mode 100644 index 000000000..3c1da7f5f --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/src/helpers/setBalance.ts @@ -0,0 +1,21 @@ +import type { NumberLike } from "../types"; +import { + getProvider, + assertValidAddress, + toRpcQuantity, +} from "../utils"; + +export async function setBalance( + address: string, + balance: NumberLike +): Promise { + const provider = await getProvider(); + + assertValidAddress(address); + const balanceHex = toRpcQuantity(balance); + + await provider.send( + "hardhat_setBalance", + [address, balanceHex], + ); +} \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/src/index.ts b/packages/hardhat-zksync-network-helpers/src/index.ts new file mode 100644 index 000000000..e205fa841 --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/src/index.ts @@ -0,0 +1,3 @@ +export {mine} from './helpers/mine' +export {impersonateAccount} from './helpers/impersonateAccount' +export {setBalance} from './helpers/setBalance' \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/src/types.ts b/packages/hardhat-zksync-network-helpers/src/types.ts new file mode 100644 index 000000000..cc17cb805 --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/src/types.ts @@ -0,0 +1,4 @@ +export type NumberLike = + | number + | bigint + | string \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/src/utils.ts b/packages/hardhat-zksync-network-helpers/src/utils.ts new file mode 100644 index 000000000..e3fb4682f --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/src/utils.ts @@ -0,0 +1,50 @@ +import { Provider } from 'zksync-ethers'; +import { NumberLike } from './types'; +import type EthereumJSUtil from "ethereumjs-util"; + + +export async function getProvider(): Promise { + const { Provider } = await import('zksync-ethers'); + const provider = new Provider('http://localhost:8011'); + return provider; +} + +export function toRpcQuantity(x: NumberLike): string { + let hex: string; + + if (typeof x === "number" || typeof x === "bigint") { + // TODO: check that number is safe + hex = `0x${x.toString(16)}`; + } else if (typeof x === "string") { + if (!x.startsWith("0x")) { + throw new Error("Only 0x-prefixed hex-encoded strings are accepted"); + } + hex = x; + } else if (x && typeof (x as any).toHexString === 'function') { + hex = (x as { toHexString: () => string }).toHexString(); + } else if (x && typeof (x as any).toString === 'function') { + hex = (x as { toString: (radix: number) => string }).toString(16); + } else { + throw new Error(`Cannot convert ${x} to an RPC quantity`); + } + + if (hex === "0x0") return hex; + + return hex.startsWith("0x") ? hex.replace(/0x0+/, "0x") : `0x${hex}`; +} + +export function assertValidAddress(address: string): void { + const { isValidChecksumAddress, isValidAddress } = + require("ethereumjs-util") as typeof EthereumJSUtil; + + if (!isValidAddress(address)) { + throw new Error(`${address} is not a valid address`); + } + + const hasChecksum = address !== address.toLowerCase(); + if (hasChecksum && !isValidChecksumAddress(address)) { + throw new Error( + `Address ${address} has an invalid checksum` + ); + } + } \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/test/tests.ts b/packages/hardhat-zksync-network-helpers/test/tests.ts new file mode 100644 index 000000000..07f60e5cd --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/test/tests.ts @@ -0,0 +1,26 @@ +import { assert } from 'console'; +import * as helpers from '../src/index' +import { Provider } from 'zksync-ethers'; + +describe('Plugin tests', async function () { + const provider = new Provider('http://localhost:8011'); + + it('tests mining blocks',async function() { + const blockNumber = await provider.getBlockNumber(); + await helpers.mine(5); + await new Promise((resolve) => setTimeout(resolve, 1500)); + const blockNumber2 = await provider.getBlockNumber(); + assert(blockNumber+5===blockNumber2) + }) + + + it('tests set balance',async function() { + const result = await provider.getAllAccountBalances("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"); + console.info(result); + helpers.setBalance("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",100n) + await new Promise((resolve) => setTimeout(resolve, 1500)); + const result2 = await provider.getAllAccountBalances("0x36615Cf349d7F6344891B1e7CA7C72883F5dc049"); + console.info(result2); + assert(result2['0x000000000000000000000000000000000000800a']===100n); + }) +}) \ No newline at end of file diff --git a/packages/hardhat-zksync-network-helpers/tsconfig.json b/packages/hardhat-zksync-network-helpers/tsconfig.json new file mode 100644 index 000000000..e6038e7fe --- /dev/null +++ b/packages/hardhat-zksync-network-helpers/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/yarn.lock b/yarn.lock index 54e65e1ed..9080ba1c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3633,7 +3633,7 @@ ethereumjs-util@^6.0.0, ethereumjs-util@^6.2.1: ethjs-util "0.1.6" rlp "^2.2.3" -ethereumjs-util@^7.0.3: +ethereumjs-util@^7.0.3, ethereumjs-util@^7.1.5: version "7.1.5" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz#9ecf04861e4fbbeed7465ece5f23317ad1129181" integrity sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==