Skip to content

Commit

Permalink
feat: wrap deploy script run with era test node for the hardhat network
Browse files Browse the repository at this point in the history
  • Loading branch information
kiriyaga committed Sep 25, 2024
1 parent 64f9617 commit 7d6f67d
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 1 deletion.
1 change: 1 addition & 0 deletions examples/zksync-ethers-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@matterlabs/hardhat-zksync-deploy": "workspace:^",
"@matterlabs/hardhat-zksync-solc": "workspace:^",
"@matterlabs/hardhat-zksync-ethers": "workspace:^",
"@matterlabs/hardhat-zksync-node": "workspace:^",
"chalk": "^4.1.2",
"hardhat": "^2.22.5",
"ethers": "^6.12.2",
Expand Down
116 changes: 116 additions & 0 deletions examples/zksync-ethers-example/scripts/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import chalk from 'chalk';
import { Signer } from 'ethers';
import hre from 'hardhat';

async function main(){
console.info(chalk.yellow(`Running deploy script 002_deploy.ts`));

console.log(await hre.network.provider.send('eth_chainId'));
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.waitForDeployment();
const greetsRunner = greets.runner as Signer;
console.info(chalk.green(`Greeter deployed to: ${await greets.getAddress()}`));
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.waitForDeployment();
const greets2Runner = greets2.runner as Signer;
console.info(chalk.green(`Greeter deployed to: ${await greets2.getAddress()}`));
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.waitForDeployment();
const greets3Runner = greets3.runner as Signer;
console.info(chalk.green(`Greeter deployed to: ${await greets3.getAddress()}`));
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.waitForDeployment();
const greets4Runner = greets4.runner as Signer;
console.info(chalk.green(`Greeter deployed to: ${await greets4.getAddress()}`));
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.waitForDeployment();
const greets5Runner = greets5.runner as Signer;
console.info(chalk.green(`Greeter deployed to: ${await greets5.getAddress()}`));
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 = await hre.ethers.provider.getSigner('0x36615Cf349d7F6344891B1e7CA7C72883F5dc049');
const contract1 = await hre.ethers.getContractAt('Greeter', await greets2.getAddress(), signer3);
const contract1Runner = contract1.runner 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, await greets3.getAddress());
console.info(chalk.green(`Greeter from getContractAt set to: ${await contract2.greet()}`));
const contract2Runner = contract2.runner 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, await greets4.getAddress());
console.info(chalk.green(`Greeter from getContractAt set to: ${await contract3.greet()}`));
const contract3Runner = contract3.runner as Signer;
console.info(chalk.green('Runner from getContractAt set to: ', await contract3Runner.getAddress()));

const newContractFactory = new hre.ethers.ContractFactory(artifact.abi, artifact.bytecode, signer);
const deployedContract = await newContractFactory.deploy("Hello World with new contract factory.");
console.info(chalk.green(`Contract with new ContractFactory deployed to ${await deployedContract.getAddress()}`))

const newContract = new hre.ethers.Contract(await deployedContract.getAddress(),artifact.abi,signer);
console.info(await newContract.greet());
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
16 changes: 16 additions & 0 deletions packages/hardhat-zksync-deploy/src/core/global-tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Network } from 'hardhat/types';

export type ZKSyncTasksWithWrappedNode = typeof global & {
_zkSyncTasksForWrapping: ZKSyncTasksForWrapping;
_zkSyncNodeNetwork?: Network;
};

export class ZKSyncTasksForWrapping {
public taskNames: string[] = [];

constructor() {}

public addTask(taskName: string) {
this.taskNames.push(taskName);
}
}
9 changes: 8 additions & 1 deletion packages/hardhat-zksync-deploy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import './type-extensions';
import { deployZkSyncContract, zkSyncDeploy, zkSyncLibraryDeploy } from './task-actions';
import { DEFAULT_DEPLOY_SCRIPTS_PATH, defaultAccountDeployerSettings } from './constants';
import { DeployerExtension } from './deployer-extension';

import { ZKSyncTasksForWrapping, ZKSyncTasksWithWrappedNode } from './core/global-tasks';
export * from './deployer';

if (!(global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping) {
(global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping = new ZKSyncTasksForWrapping();
}

extendConfig((config, userConfig) => {
config.paths.deployPaths = userConfig.paths?.deployPaths ?? DEFAULT_DEPLOY_SCRIPTS_PATH;
config.deployerAccounts = { ...defaultAccountDeployerSettings, ...userConfig?.deployerAccounts };
Expand Down Expand Up @@ -71,6 +75,9 @@ task(TASK_DEPLOY_ZKSYNC_CONTRACT, 'Runs the deploy scripts for ZKsync network')
.addFlag('noCompile', 'Flag to disable auto compilation')
.setAction(deployZkSyncContract);

(global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping.addTask(TASK_DEPLOY_ZKSYNC);
(global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping.addTask(TASK_DEPLOY_ZKSYNC_LIBRARIES);
(global as ZKSyncTasksWithWrappedNode)._zkSyncTasksForWrapping.addTask(TASK_DEPLOY_ZKSYNC_CONTRACT);
try {
require.resolve('zksync2-js');
console.info(chalk.red('The package zksync2-js is deprecated. Please use zksync-ethers.'));
Expand Down
51 changes: 51 additions & 0 deletions packages/hardhat-zksync-node/src/core/global-interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { RunSuperFunction, TaskArguments } from 'hardhat/types';
import { GlobalWithHardhatContext } from 'hardhat/src/internal/context';
import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins';
import { configureNetwork, startServer, waitForNodeToBeReady } from '../utils';
import { ZKSyncTasksWithWrappedNode } from './global-task';

export function interceptAndWrapTasksWithNode() {
const zkSyncGlobal = global as ZKSyncTasksWithWrappedNode & GlobalWithHardhatContext;
const taskMap = zkSyncGlobal.__hardhatContext.tasksDSL.getTaskDefinitions();

if (!zkSyncGlobal._zkSyncTasksForWrapping) {
return;
}

zkSyncGlobal._zkSyncTasksForWrapping.taskNames.forEach((taskName) => {
const foundTask = taskMap[taskName];

if (!foundTask) {
return;
}

if (foundTask.isSubtask) {
zkSyncGlobal.__hardhatContext.tasksDSL.subtask(foundTask.name, foundTask.description, wrapTaskWithNode);
}

zkSyncGlobal.__hardhatContext.tasksDSL.task(foundTask.name, foundTask.description, wrapTaskWithNode);
});
}

async function wrapTaskWithNode(taskArgs: TaskArguments, env: any, runSuper: RunSuperFunction<TaskArguments>) {
if (env.network.zksync !== true || env.network.name !== HARDHAT_NETWORK_NAME) {
return await runSuper(taskArgs);
}
const zkSyncGlobal = global as ZKSyncTasksWithWrappedNode;
const { commandArgs, server, port } = await startServer();
try {
await server.listen(commandArgs, false);
await waitForNodeToBeReady(port);
const oldNetwork = env.network;
await configureNetwork(env.config, env.network, port);
env.injectToGlobal();
zkSyncGlobal._zkSyncNodeNetwork = env.network;
const result = await runSuper(taskArgs);
env.network = oldNetwork;
delete zkSyncGlobal._zkSyncNodeNetwork;
env.injectToGlobal();
return result;
} finally {
await server.stop();
}
}
16 changes: 16 additions & 0 deletions packages/hardhat-zksync-node/src/core/global-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Network } from 'hardhat/types';

export type ZKSyncTasksWithWrappedNode = typeof global & {
_zkSyncTasksForWrapping: ZKSyncTasksForWrapping;
_zkSyncNodeNetwork?: Network;
};

export class ZKSyncTasksForWrapping {
public taskNames: string[] = [];

constructor() {}

public addTask(taskName: string) {
this.taskNames.push(taskName);
}
}
Empty file.
102 changes: 102 additions & 0 deletions packages/hardhat-zksync-node/src/core/script-runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { isRunningHardhatCoreTests } from 'hardhat/internal/core/execution-mode';
import { HardhatArguments } from 'hardhat/types';
import { getEnvVariablesMap } from 'hardhat/internal/core/params/env-variables';
import { HardhatContext } from 'hardhat/internal/context';
import { request } from 'http';
import { configureNetwork, startServer, waitForNodeToBeReady } from '../utils';

export async function runScript(
scriptPath: string,
scriptArgs: string[] = [],
extraNodeArgs: string[] = [],
extraEnvVars: { [name: string]: string } = {},
): Promise<number> {
const { fork } = await import('child_process');
const processExecArgv = withFixedInspectArg(process.execArgv);

const nodeArgs = [
...processExecArgv,
...getTsNodeArgsIfNeeded(scriptPath, extraEnvVars.HARDHAT_TYPECHECK === 'true'),
...extraNodeArgs,
];

const envVars = { ...process.env, ...extraEnvVars };

return new Promise((resolve, reject) => {
const childProcess = fork(scriptPath, scriptArgs, {
stdio: 'inherit',
execArgv: nodeArgs,
env: envVars,
});

let runnedServer: any;

childProcess.on('spawn', () => {
return new Promise(async (_, rejectChild) => {
try {
request('hardhat/register');
const ctx = HardhatContext.getHardhatContext();
const { commandArgs, server, port } = await startServer();
runnedServer = server;
await server.listen(commandArgs, false);
await waitForNodeToBeReady(port);
await configureNetwork(ctx.environment!.config, ctx.environment!.network, port);
} catch (error) {
rejectChild(error);
}
});
});

childProcess.once('close', (status) => {
runnedServer?.stop();
resolve(status as number);
});

childProcess.once('error', (error) => {
runnedServer?.stop();
reject(error);
});
});
}

export async function runScriptWithHardhat(
hardhatArguments: HardhatArguments,
scriptPath: string,
scriptArgs: string[] = [],
extraNodeArgs: string[] = [],
extraEnvVars: { [name: string]: string } = {},
): Promise<number> {
return runScript(scriptPath, scriptArgs, [...extraNodeArgs], {
...getEnvVariablesMap(hardhatArguments),
...extraEnvVars,
});
}

function withFixedInspectArg(argv: string[]) {
const fixIfInspectArg = (arg: string) => {
if (arg.toLowerCase().includes('--inspect-brk=')) {
return '--inspect';
}
return arg;
};
return argv.map(fixIfInspectArg);
}

function getTsNodeArgsIfNeeded(scriptPath: string, shouldTypecheck: boolean): string[] {
if (process.execArgv.includes('ts-node/register')) {
return [];
}

// if we are running the tests we only want to transpile, or these tests
// take forever
if (isRunningHardhatCoreTests()) {
return ['--require', 'ts-node/register/transpile-only'];
}

// If the script we are going to run is .ts we need ts-node
if (/\.tsx?$/i.test(scriptPath)) {
return ['--require', `ts-node/register${shouldTypecheck ? '' : '/transpile-only'}`];
}

return [];
}
14 changes: 14 additions & 0 deletions packages/hardhat-zksync-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import { task, subtask, types } from 'hardhat/config';
import {
TASK_COMPILE,
TASK_NODE,
TASK_RUN,
TASK_TEST,
TASK_TEST_GET_TEST_FILES,
TASK_TEST_RUN_MOCHA_TESTS,
} from 'hardhat/builtin-tasks/task-names';

import { HARDHAT_NETWORK_NAME } from 'hardhat/plugins';
import { TaskArguments } from 'hardhat/types';
import path from 'path';
import {
MAX_PORT_ATTEMPTS,
START_PORT,
Expand All @@ -30,6 +32,16 @@ import {
} from './utils';
import { RPCServerDownloader } from './downloader';
import { ZkSyncNodePluginError } from './errors';
import { interceptAndWrapTasksWithNode } from './core/global-interceptor';
import { runScriptWithHardhat } from './core/script-runner';

task(TASK_RUN).setAction(async (args, hre, runSuper) => {
if (!hre.network.zksync) {
await runSuper(args, hre);
}

await runScriptWithHardhat(hre.hardhatArguments, path.resolve(args.script));
});

// Subtask to download the binary
subtask(TASK_NODE_ZKSYNC_DOWNLOAD_BINARY, 'Downloads the JSON-RPC server binary')
Expand Down Expand Up @@ -352,3 +364,5 @@ task(
}
},
);

interceptAndWrapTasksWithNode();
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7d6f67d

Please sign in to comment.