Skip to content

Commit

Permalink
feat: task to stop localnet (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
fadeev authored Sep 12, 2024
1 parent 59a77e5 commit 95f0945
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 39 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Run Localnet

on:
pull_request:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
start:
runs-on: ubuntu-latest

steps:
- name: Checkout Repository
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "21"
registry-url: "https://registry.npmjs.org"

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Install Dependencies
run: yarn

- name: Start Localnet
run: yarn hardhat localnet --stop-after-init
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "./packages/tasks/src/localnet";
import "./packages/tasks/src/stop";

import { HardhatUserConfig } from "hardhat/config";

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"typescript": "^5.5.4"
},
"dependencies": {
"@inquirer/prompts": "^5.5.0",
"@uniswap/v2-core": "^1.0.1",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@zetachain/protocol-contracts": "10.0.0-rc10",
Expand Down
3 changes: 3 additions & 0 deletions packages/localnet/src/createToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ export const createToken = async ({
deployOpts
);

await zrc20.waitForDeployment();

if (!isGasToken) {
const erc20Factory = new ethers.ContractFactory(
TestERC20.abi,
TestERC20.bytecode,
deployer
);
erc20 = await erc20Factory.deploy(symbol, symbol, deployOpts);
await erc20.waitForDeployment();
const erc20Decimals = await (erc20 as any).connect(deployer).decimals();

await (erc20 as any)
Expand Down
1 change: 1 addition & 0 deletions packages/tasks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { localnetTask } from "./localnet";
export { localnetStopTask } from "./stop";
165 changes: 129 additions & 36 deletions packages/tasks/src/localnet.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,165 @@
import { task, types } from "hardhat/config";
import { initLocalnet } from "../../localnet/src";
import { exec } from "child_process";
import { exec, execSync } from "child_process";
import waitOn from "wait-on";
import ansis from "ansis";
import fs from "fs";
import { confirm } from "@inquirer/prompts";

const main = async (args: any) => {
const port = args.port || 8545;
const anvilArgs = args.anvil ? `${args.anvil}` : "";
const LOCALNET_PID_FILE = "./localnet.pid";

console.log(`Starting anvil on port ${port} with args: ${anvilArgs}`);
const killProcessOnPort = async (port: number, forceKill: boolean) => {
try {
const output = execSync(`lsof -ti tcp:${port}`).toString().trim();
if (output) {
console.log(
ansis.yellow(`Port ${port} is already in use by process ${output}.`)
);

if (forceKill) {
execSync(`kill -9 ${output}`);
console.log(
ansis.green(`Successfully killed process ${output} on port ${port}.`)
);
} else {
const answer = await confirm({
message: `Do you want to kill the process running on port ${port}?`,
default: true,
});

if (answer) {
execSync(`kill -9 ${output}`);
console.log(
ansis.green(
`Successfully killed process ${output} on port ${port}.`
)
);
} else {
console.log(ansis.red("Process not killed. Exiting..."));
process.exit(1);
}
}
}
} catch (error) {
// Silently continue if no process is found or killing fails
}
};

const localnet = async (args: any) => {
try {
execSync("which anvil");
} catch (error) {
console.error(
ansis.red(
"Error: 'anvil' not found. Please install Foundry: https://getfoundry.sh"
)
);
process.exit(1);
}

await killProcessOnPort(args.port, args.forceKill);

if (args.anvil !== "")
console.log(`Starting anvil on port ${args.port} with args: ${args.anvil}`);

const anvilProcess = exec(
`anvil --auto-impersonate --port ${port} ${anvilArgs}`
`anvil --auto-impersonate --port ${args.port} ${args.anvil}`
);

if (anvilProcess.stdout && anvilProcess.stderr) {
anvilProcess.stdout.pipe(process.stdout);
anvilProcess.stderr.pipe(process.stderr);
}

await waitOn({ resources: [`tcp:127.0.0.1:${port}`] });
await waitOn({ resources: [`tcp:127.0.0.1:${args.port}`] });

const addr = await initLocalnet(port);
const cleanup = () => {
console.log("\nShutting down anvil and cleaning up...");
if (anvilProcess) {
anvilProcess.kill();
}
if (fs.existsSync(LOCALNET_PID_FILE)) {
fs.unlinkSync(LOCALNET_PID_FILE);
}
};

console.log(ansis.cyan`
EVM Contract Addresses
======================
try {
const addr = await initLocalnet(args.port);

Gateway EVM: ${addr.gatewayEVM}
ERC-20 custody: ${addr.custodyEVM}
TSS: ${addr.tssEVM}
ZETA: ${addr.zetaEVM}`);
// EVM Contract Addresses
const evmHeader = "\nEVM Contract Addresses";
console.log(ansis.cyan(`${evmHeader}\n${"=".repeat(evmHeader.length)}`));

addr.foreignCoins
.filter((coin: any) => coin.asset !== "")
.forEach((coin: any) => {
console.log(ansis.cyan`ERC-20 ${coin.symbol}: ${coin.asset}`);
});
const evmAddresses = {
"Gateway EVM": addr.gatewayEVM,
"ERC-20 Custody": addr.custodyEVM,
TSS: addr.tssEVM,
ZETA: addr.zetaEVM,
...addr.foreignCoins
.filter((coin: any) => coin.asset !== "")
.reduce((acc: any, coin: any) => {
acc[`ERC-20 ${coin.symbol}`] = coin.asset;
return acc;
}, {}),
};

console.log(ansis.green`
ZetaChain Contract Addresses
============================
console.table(evmAddresses);

Gateway ZetaChain: ${addr.gatewayZetaChain}
ZETA: ${addr.zetaZetaChain}
Fungible module: ${addr.fungibleModuleZetaChain}
System contract: ${addr.sytemContractZetaChain}`);
const zetaHeader = "\nZetaChain Contract Addresses";
console.log(ansis.green(`${zetaHeader}\n${"=".repeat(zetaHeader.length)}`));

addr.foreignCoins.forEach((coin: any) => {
console.log(
ansis.green`ZRC-20 ${coin.symbol}: ${coin.zrc20_contract_address}`
);
});
const zetaAddresses = {
"Gateway ZetaChain": addr.gatewayZetaChain,
ZETA: addr.zetaZetaChain,
"Fungible Module": addr.fungibleModuleZetaChain,
"System Contract": addr.sytemContractZetaChain,
...addr.foreignCoins.reduce((acc: any, coin: any) => {
acc[`ZRC-20 ${coin.symbol}`] = coin.zrc20_contract_address;
return acc;
}, {}),
};

console.table(zetaAddresses);

fs.writeFileSync(LOCALNET_PID_FILE, process.pid.toString(), "utf-8");
} catch (error: any) {
console.error(ansis.red`Error initializing localnet: ${error}`);
cleanup();
process.exit(1);
}

process.on("SIGINT", () => {
console.log("\nReceived Ctrl-C, shutting down anvil...");
anvilProcess.kill();
const handleExit = (signal: string) => {
console.log(`\nReceived ${signal}, shutting down...`);
cleanup();
process.exit();
};

process.on("SIGINT", () => handleExit("SIGINT"));
process.on("SIGTERM", () => handleExit("SIGTERM"));

process.on("exit", () => {
console.log("Process exiting...");
});

if (args.stopAfterInit) {
console.log(ansis.green("Localnet successfully initialized. Stopping..."));
cleanup();
process.exit(0);
}

await new Promise(() => {});
};

export const localnetTask = task("localnet", "Start localnet", main)
export const localnetTask = task("localnet", "Start localnet", localnet)
.addOptionalParam("port", "Port to run anvil on", 8545, types.int)
.addOptionalParam(
"anvil",
"Additional arguments to pass to anvil",
"",
types.string
)
.addFlag("forceKill", "Force kill any process on the port without prompting")
.addFlag(
"stopAfterInit",
"Stop the localnet after successful initialization"
);
28 changes: 28 additions & 0 deletions packages/tasks/src/stop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { task } from "hardhat/config";
import fs from "fs";
import ansis from "ansis";

const LOCALNET_PID_FILE = "./localnet.pid";

const localnetStop = async (args: any) => {
if (!fs.existsSync(LOCALNET_PID_FILE)) {
console.log(ansis.red("Localnet is not running or PID file is missing."));
return;
}

const pid = fs.readFileSync(LOCALNET_PID_FILE, "utf-8").trim();
try {
process.kill(Number(pid));
console.log(ansis.green(`Successfully stopped localnet (PID: ${pid})`));
fs.unlinkSync(LOCALNET_PID_FILE);
console.log(ansis.green(`PID file ${LOCALNET_PID_FILE} removed.`));
} catch (err) {
console.error(ansis.red(`Failed to stop localnet: ${err}`));
}
};

export const localnetStopTask = task(
"localnet-stop",
"Stop localnet",
localnetStop
);
Loading

0 comments on commit 95f0945

Please sign in to comment.