From b4bba7fdf9f74d6958e7c4bf8b7c34fe10bf1acc Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Wed, 23 Apr 2025 23:17:07 -0300 Subject: [PATCH 1/3] chore: updating versions --- .env.example | 4 ++++ esbuild.config.dev.js | 2 +- esbuild.config.prod.js | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- src/lib/config/simulator.ts | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 4fb79844..2a0a180d 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,7 @@ FRONTEND_BUILD_TARGET=final VITE_JSON_RPC_SERVER_URL='http://127.0.0.1:4000/api' # if VITE_PROXY_ENABLED = 'true' change to '/api' VITE_WS_SERVER_URL= 'ws://127.0.0.1:4000' # if VITE_PROXY_ENABLED = 'true' change to '/' VITE_PLAUSIBLE_DOMAIN='studio.genlayer.com' +VITE_FINALITY_WINDOW_APPEAL_FAILED_REDUCTION=0.2 # GenVM Configuration @@ -77,6 +78,9 @@ FRONTEND_BUILD_TARGET = 'final' # change to 'dev' to run in dev mode HARDHAT_URL = 'http://hardhat' HARDHAT_PORT = '8545' HARDHAT_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' +HARDHAT_CHAIN_ID=61999 +COMPOSE_PROFILES='hardhat' BACKEND_BUILD_TARGET = 'debug' VITE_FINALITY_WINDOW=1 +DEFAULT_CONSENSUS_MAX_ROTATIONS=3 diff --git a/esbuild.config.dev.js b/esbuild.config.dev.js index 9e8495e2..cffca383 100644 --- a/esbuild.config.dev.js +++ b/esbuild.config.dev.js @@ -11,7 +11,7 @@ export default { banner: { js: `const _importMetaUrl = new URL(import.meta.url).pathname;`, }, - external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2", "fs-extra"] + external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2", "fs-extra", "esbuild"] }, watch: true, }; diff --git a/esbuild.config.prod.js b/esbuild.config.prod.js index 190179a3..9d14dbd9 100644 --- a/esbuild.config.prod.js +++ b/esbuild.config.prod.js @@ -11,7 +11,7 @@ export default { banner: { js: `const _importMetaUrl = new URL(import.meta.url).pathname;`, }, - external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2", "fs-extra"] + external: ["commander", "dockerode", "dotenv", "ethers", "inquirer", "update-check", "ssh2", "fs-extra", "esbuild"] }, watch: false, }; diff --git a/package-lock.json b/package-lock.json index d8ca7b56..da6d9869 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "dotenv": "^16.4.5", "ethers": "^6.13.4", "fs-extra": "^11.3.0", - "genlayer-js": "^0.6.0", + "genlayer-js": "^0.9.0", "inquirer": "^12.0.0", "node-fetch": "^3.0.0", "open": "^10.1.0", @@ -5281,9 +5281,9 @@ } }, "node_modules/genlayer-js": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-0.6.4.tgz", - "integrity": "sha512-tbsBRyVUGZ0a+661ML6wu3U3p5CtHkik7a4qTc68OZgf1pX1G4JBs7CzAmqAkMt8HUyD/J413raDs9Q5s3psWw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/genlayer-js/-/genlayer-js-0.9.0.tgz", + "integrity": "sha512-PBG9k7g9/uvl33DIs9bVTb+bdaHnbmXwphsh4fAUmv5CLTLKQtfLqQ7+OXFFVq56txs9geldr+g7ulgd8YtCGQ==", "license": "MIT", "dependencies": { "eslint-plugin-import": "^2.30.0", diff --git a/package.json b/package.json index 2f088a8e..8289b60c 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "dotenv": "^16.4.5", "ethers": "^6.13.4", "fs-extra": "^11.3.0", - "genlayer-js": "^0.6.0", + "genlayer-js": "^0.9.0", "inquirer": "^12.0.0", "node-fetch": "^3.0.0", "open": "^10.1.0", diff --git a/src/lib/config/simulator.ts b/src/lib/config/simulator.ts index de767ad4..d77bec04 100644 --- a/src/lib/config/simulator.ts +++ b/src/lib/config/simulator.ts @@ -1,4 +1,4 @@ -export const localnetCompatibleVersion = "v0.42.0"; +export const localnetCompatibleVersion = "v0.51.0"; export const DEFAULT_JSON_RPC_URL = "http://localhost:4000/api"; export const CONTAINERS_NAME_PREFIX = "/genlayer-"; export const IMAGES_NAME_PREFIX = "yeagerai"; From 8bb848e7ad4c9e37e585ae22122a727db64ca53c Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Thu, 24 Apr 2025 02:51:11 -0300 Subject: [PATCH 2/3] feat: create an account if it doesnt exists --- src/commands/contracts/call.ts | 30 +++--- src/commands/contracts/deploy.ts | 29 +++--- src/commands/keygen/create.ts | 30 +----- src/lib/accounts/KeypairManager.ts | 43 ++++++++ src/lib/accounts/getPrivateKey.ts | 21 ---- src/lib/actions/BaseAction.ts | 14 ++- tests/actions/call.test.ts | 4 +- tests/actions/create.test.ts | 83 +++------------- tests/actions/deploy.test.ts | 6 +- tests/libs/accounts/KeypairManager.test.ts | 110 +++++++++++++++++++++ tests/libs/baseAction.test.ts | 40 ++++++++ tests/libs/getPrivateKey.test.ts | 96 ------------------ 12 files changed, 262 insertions(+), 244 deletions(-) create mode 100644 src/lib/accounts/KeypairManager.ts delete mode 100644 src/lib/accounts/getPrivateKey.ts create mode 100644 tests/libs/accounts/KeypairManager.test.ts delete mode 100644 tests/libs/getPrivateKey.test.ts diff --git a/src/commands/contracts/call.ts b/src/commands/contracts/call.ts index 5b00cdd3..73111940 100644 --- a/src/commands/contracts/call.ts +++ b/src/commands/contracts/call.ts @@ -1,7 +1,6 @@ import { createClient, createAccount } from "genlayer-js"; import { simulator } from "genlayer-js/chains"; import type { GenLayerClient } from "genlayer-js/types"; -import { getPrivateKey } from "../../lib/accounts/getPrivateKey"; import { BaseAction } from "../../lib/actions/BaseAction"; export interface CallOptions { @@ -9,15 +8,21 @@ export interface CallOptions { } export class CallAction extends BaseAction{ - private genlayerClient: GenLayerClient; + private _genlayerClient: GenLayerClient | null = null; constructor() { super(); - this.genlayerClient = createClient({ - chain: simulator, - endpoint: process.env.VITE_JSON_RPC_SERVER_URL, - account: createAccount(getPrivateKey() as any), - }); + } + + private async getClient(): Promise> { + if (!this._genlayerClient) { + this._genlayerClient = createClient({ + chain: simulator, + endpoint: process.env.VITE_JSON_RPC_SERVER_URL, + account: createAccount(await this.getPrivateKey() as any), + }); + } + return this._genlayerClient; } async call({ @@ -29,9 +34,10 @@ export class CallAction extends BaseAction{ method: string; args: any[]; }): Promise { + const client = await this.getClient(); this.startSpinner(`Calling method ${method} on contract at ${contractAddress}...`); - const contractSchema = await this.genlayerClient.getContractSchema(contractAddress); + const contractSchema = await client.getContractSchema(contractAddress); if(!contractSchema.methods.hasOwnProperty(method)){ this.failSpinner(`method ${method} not found.`); @@ -50,7 +56,8 @@ export class CallAction extends BaseAction{ private async executeRead(contractAddress: string, method: string, args: any[]): Promise { try { - const result = await this.genlayerClient.readContract({ + const client = await this.getClient(); + const result = await client.readContract({ address: contractAddress as any, functionName: method, args, @@ -63,13 +70,14 @@ export class CallAction extends BaseAction{ private async executeWrite(contractAddress: string, method: string, args: any[]): Promise { try { - const hash = await this.genlayerClient.writeContract({ + const client = await this.getClient(); + const hash = await client.writeContract({ address: contractAddress as any, functionName: method, args, value: 0n, }); - const result = await this.genlayerClient.waitForTransactionReceipt({ + const result = await client.waitForTransactionReceipt({ hash, retries: 15, interval: 2000, diff --git a/src/commands/contracts/deploy.ts b/src/commands/contracts/deploy.ts index 66e3aef3..04b904c1 100644 --- a/src/commands/contracts/deploy.ts +++ b/src/commands/contracts/deploy.ts @@ -3,7 +3,6 @@ import path from "path"; import { createClient, createAccount } from "genlayer-js"; import { simulator } from "genlayer-js/chains"; import type { GenLayerClient } from "genlayer-js/types"; -import { getPrivateKey } from "../../lib/accounts/getPrivateKey"; import { BaseAction } from "../../lib/actions/BaseAction"; import { pathToFileURL } from "url"; import { TransactionStatus } from "genlayer-js/types"; @@ -15,16 +14,22 @@ export interface DeployOptions { } export class DeployAction extends BaseAction { - private genlayerClient: GenLayerClient; + private _genlayerClient: GenLayerClient | null = null; private readonly deployDir = path.resolve(process.cwd(), "deploy"); constructor() { super(); - this.genlayerClient = createClient({ - chain: simulator, - endpoint: process.env.VITE_JSON_RPC_SERVER_URL, - account: createAccount(getPrivateKey() as any), - }); + } + + private async getClient(): Promise> { + if (!this._genlayerClient) { + this._genlayerClient = createClient({ + chain: simulator, + endpoint: process.env.VITE_JSON_RPC_SERVER_URL, + account: createAccount(await this.getPrivateKey() as any), + }); + } + return this._genlayerClient; } private readContractCode(contractPath: string): string { @@ -63,7 +68,8 @@ export class DeployAction extends BaseAction { this.failSpinner(`No "default" function found in: ${filePath}`); return } - await module.default(this.genlayerClient); + const client = await this.getClient(); + await module.default(client); this.succeedSpinner(`Successfully executed: ${filePath}`); } catch (error) { this.failSpinner(`Error executing: ${filePath}`, error); @@ -112,8 +118,9 @@ export class DeployAction extends BaseAction { async deploy(options: DeployOptions): Promise { try { + const client = await this.getClient(); this.startSpinner("Setting up the deployment environment..."); - await this.genlayerClient.initializeConsensusSmartContract(); + await client.initializeConsensusSmartContract(); if (!options.contract) { this.failSpinner("No contract specified for deployment."); @@ -133,8 +140,8 @@ export class DeployAction extends BaseAction { this.setSpinnerText("Starting contract deployment..."); this.log("Deployment Parameters:", deployParams); - const hash = (await this.genlayerClient.deployContract(deployParams)) as any; - const result = await this.genlayerClient.waitForTransactionReceipt({ + const hash = (await client.deployContract(deployParams)) as any; + const result = await client.waitForTransactionReceipt({ hash, retries: 15, interval: 2000, diff --git a/src/commands/keygen/create.ts b/src/commands/keygen/create.ts index 8509d090..1878d31c 100644 --- a/src/commands/keygen/create.ts +++ b/src/commands/keygen/create.ts @@ -1,5 +1,3 @@ -import { writeFileSync, existsSync } from "fs"; -import { ethers } from "ethers"; import { BaseAction } from "../../lib/actions/BaseAction"; export interface CreateKeypairOptions { @@ -7,36 +5,18 @@ export interface CreateKeypairOptions { overwrite: boolean; } -export class KeypairCreator extends BaseAction{ - +export class KeypairCreator extends BaseAction { constructor() { - super() + super(); } createKeypairAction(options: CreateKeypairOptions) { try { this.startSpinner(`Creating keypair...`); - const outputPath = this.getFilePath(options.output); - - if(existsSync(outputPath) && !options.overwrite) { - this.failSpinner( - `The file at ${outputPath} already exists. Use the '--overwrite' option to replace it.` - ); - return; - } - - const wallet = ethers.Wallet.createRandom(); - const keypairData = { - address: wallet.address, - privateKey: wallet.privateKey, - }; - - writeFileSync(outputPath, JSON.stringify(keypairData, null, 2)); - - this.writeConfig('keyPairPath', outputPath); - this.succeedSpinner(`Keypair successfully created and saved to: ${outputPath}`); + this.keypairManager.createKeypair(options.output, options.overwrite); + this.succeedSpinner(`Keypair successfully created and saved to: ${options.output}`); } catch (error) { - this.failSpinner("Failed to generate keypair:", error); + this.failSpinner("Failed to generate keypair", error); } } } \ No newline at end of file diff --git a/src/lib/accounts/KeypairManager.ts b/src/lib/accounts/KeypairManager.ts new file mode 100644 index 00000000..3d4831dc --- /dev/null +++ b/src/lib/accounts/KeypairManager.ts @@ -0,0 +1,43 @@ +import { writeFileSync, existsSync, readFileSync } from "fs"; +import { ethers } from "ethers"; +import path from "path"; +import { ConfigFileManager } from "../config/ConfigFileManager"; + +export class KeypairManager extends ConfigFileManager { + constructor() { + super(); + } + + getPrivateKey(): string | undefined { + const keypairPath = this.getConfigByKey("keyPairPath"); + + if (!keypairPath || !existsSync(keypairPath)) { + return "" + } + + const keypairData = JSON.parse(readFileSync(keypairPath, "utf-8")); + + if (!keypairData.privateKey) { + return "" + } + + return keypairData.privateKey; + } + + createKeypair(outputPath = "./keypair.json", overwrite: boolean = false): void { + const finalOutputPath = this.getFilePath(outputPath); + + if(existsSync(finalOutputPath) && !overwrite) { + throw new Error(`The file at ${finalOutputPath} already exists. Use the '--overwrite' option to replace it.`); + } + + const wallet = ethers.Wallet.createRandom(); + const keypairData = { + address: wallet.address, + privateKey: wallet.privateKey, + }; + + writeFileSync(finalOutputPath, JSON.stringify(keypairData, null, 2)); + this.writeConfig('keyPairPath', finalOutputPath); + } +} \ No newline at end of file diff --git a/src/lib/accounts/getPrivateKey.ts b/src/lib/accounts/getPrivateKey.ts deleted file mode 100644 index 42001249..00000000 --- a/src/lib/accounts/getPrivateKey.ts +++ /dev/null @@ -1,21 +0,0 @@ -import fs from "fs"; -import { ConfigFileManager } from "../config/ConfigFileManager"; - -export function getPrivateKey(): string { - const configFileManager: ConfigFileManager = new ConfigFileManager(); - const keypairPath = configFileManager.getConfigByKey("keyPairPath"); - - if (!keypairPath || !fs.existsSync(keypairPath)) { - console.error("Keypair file not found. Please generate or specify a valid keypair path."); - process.exit(1); - } - - const keypairData = JSON.parse(fs.readFileSync(keypairPath, "utf-8")); - - if (!keypairData.privateKey) { - console.error("Invalid keypair file. Private key is missing."); - process.exit(1); - } - - return keypairData.privateKey; -} \ No newline at end of file diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts index 6702a803..ad1bbda6 100644 --- a/src/lib/actions/BaseAction.ts +++ b/src/lib/actions/BaseAction.ts @@ -2,14 +2,26 @@ import { ConfigFileManager } from "../../lib/config/ConfigFileManager"; import ora, { Ora } from "ora"; import chalk from "chalk"; import inquirer from "inquirer"; - +import { KeypairManager } from "../accounts/KeypairManager"; export class BaseAction extends ConfigFileManager { private spinner: Ora; + protected keypairManager: KeypairManager; constructor() { super() this.spinner = ora({ text: "", spinner: "dots" }); + this.keypairManager = new KeypairManager(); + } + + protected async getPrivateKey() { + const privateKey = this.keypairManager.getPrivateKey(); + if (privateKey) { + return privateKey; + } + await this.confirmPrompt("Keypair file not found. Would you like to create a new keypair?"); + this.keypairManager.createKeypair(); + return this.keypairManager.getPrivateKey(); } protected async confirmPrompt(message: string): Promise { diff --git a/tests/actions/call.test.ts b/tests/actions/call.test.ts index 6386e05e..1f7a10ca 100644 --- a/tests/actions/call.test.ts +++ b/tests/actions/call.test.ts @@ -1,10 +1,8 @@ import { describe, test, vi, beforeEach, afterEach, expect } from "vitest"; import { createClient, createAccount } from "genlayer-js"; import { CallAction } from "../../src/commands/contracts/call"; -import { getPrivateKey } from "../../src/lib/accounts/getPrivateKey"; vi.mock("genlayer-js"); -vi.mock("../../src/lib/accounts/getPrivateKey"); describe("CallAction", () => { let callActions: CallAction; @@ -21,8 +19,8 @@ describe("CallAction", () => { vi.clearAllMocks(); vi.mocked(createClient).mockReturnValue(mockClient as any); vi.mocked(createAccount).mockReturnValue({ privateKey: mockPrivateKey } as any); - vi.mocked(getPrivateKey).mockReturnValue(mockPrivateKey); callActions = new CallAction(); + vi.spyOn(callActions as any, "getPrivateKey").mockResolvedValue(mockPrivateKey); vi.spyOn(callActions as any, "startSpinner").mockImplementation(() => {}); vi.spyOn(callActions as any, "succeedSpinner").mockImplementation(() => {}); diff --git a/tests/actions/create.test.ts b/tests/actions/create.test.ts index e111c2ba..2eff9449 100644 --- a/tests/actions/create.test.ts +++ b/tests/actions/create.test.ts @@ -20,10 +20,15 @@ describe("KeypairCreator", () => { address: "0xMockedAddress", privateKey: "0xMockedPrivateKey", }; - keypairCreator = new KeypairCreator(); + + const mockKeypairManager = { + createKeypair: vi.fn(), + }; beforeEach(() => { vi.clearAllMocks(); + keypairCreator = new KeypairCreator(); + (keypairCreator as any).keypairManager = mockKeypairManager; vi.spyOn(keypairCreator as any, "startSpinner").mockImplementation(() => {}); vi.spyOn(keypairCreator as any, "succeedSpinner").mockImplementation(() => {}); vi.spyOn(keypairCreator as any, "failSpinner").mockImplementation(() => {}); @@ -43,86 +48,20 @@ describe("KeypairCreator", () => { keypairCreator.createKeypairAction(options); expect(keypairCreator["startSpinner"]).toHaveBeenCalledWith("Creating keypair..."); - expect(ethers.Wallet.createRandom).toHaveBeenCalledTimes(1); - expect(keypairCreator["getFilePath"]).toHaveBeenCalledWith("keypair.json"); - - - expect(writeFileSync).toHaveBeenCalledWith( - "/mocked/path/keypair.json", - JSON.stringify( - { - address: mockWallet.address, - privateKey: mockWallet.privateKey, - }, - null, - 2 - ) - ); - - expect(keypairCreator["writeConfig"]).toHaveBeenCalledWith( - "keyPairPath", - "/mocked/path/keypair.json" - ); - + expect(mockKeypairManager.createKeypair).toHaveBeenCalledWith(options.output, options.overwrite); expect(keypairCreator["succeedSpinner"]).toHaveBeenCalledWith( - "Keypair successfully created and saved to: /mocked/path/keypair.json" - ); - }); - - test("skips creation if file exists and overwrite is false", () => { - vi.mocked(existsSync).mockReturnValue(true); - const options = { output: "keypair.json", overwrite: false }; - - keypairCreator.createKeypairAction(options); - - expect(ethers.Wallet.createRandom).not.toHaveBeenCalled(); - expect(writeFileSync).not.toHaveBeenCalled(); - expect(keypairCreator["failSpinner"]).toHaveBeenCalledWith( - "The file at /mocked/path/keypair.json already exists. Use the '--overwrite' option to replace it." - ); - }); - - test("overwrites the file if overwrite is true", () => { - vi.mocked(existsSync).mockReturnValue(true); - const options = { output: "keypair.json", overwrite: true }; - - keypairCreator.createKeypairAction(options); - - expect(keypairCreator["startSpinner"]).toHaveBeenCalledWith("Creating keypair..."); - expect(ethers.Wallet.createRandom).toHaveBeenCalledTimes(1); - expect(keypairCreator["getFilePath"]).toHaveBeenCalledWith("keypair.json"); - - expect(writeFileSync).toHaveBeenCalledWith( - "/mocked/path/keypair.json", - JSON.stringify( - { - address: mockWallet.address, - privateKey: mockWallet.privateKey, - }, - null, - 2 - ) - ); - - expect(keypairCreator["writeConfig"]).toHaveBeenCalledWith( - "keyPairPath", - "/mocked/path/keypair.json" - ); - - expect(keypairCreator["succeedSpinner"]).toHaveBeenCalledWith( - "Keypair successfully created and saved to: /mocked/path/keypair.json" + "Keypair successfully created and saved to: keypair.json" ); }); test("handles errors during keypair creation", () => { - const mockError = new Error("Mocked write error"); - - vi.mocked(writeFileSync).mockImplementation(() => { + const mockError = new Error("Mocked creation error"); + mockKeypairManager.createKeypair.mockImplementation(() => { throw mockError; }); keypairCreator.createKeypairAction({ output: "keypair.json", overwrite: true }); - expect(keypairCreator["failSpinner"]).toHaveBeenCalledWith("Failed to generate keypair:", mockError); + expect(keypairCreator["failSpinner"]).toHaveBeenCalledWith("Failed to generate keypair", mockError); }); }); diff --git a/tests/actions/deploy.test.ts b/tests/actions/deploy.test.ts index 8e8dfbdd..9df3ccc6 100644 --- a/tests/actions/deploy.test.ts +++ b/tests/actions/deploy.test.ts @@ -2,7 +2,6 @@ import { describe, test, vi, beforeEach, afterEach, expect } from "vitest"; import fs from "fs"; import { createClient, createAccount } from "genlayer-js"; import { DeployAction, DeployOptions } from "../../src/commands/contracts/deploy"; -import { getPrivateKey } from "../../src/lib/accounts/getPrivateKey"; import { buildSync } from "esbuild"; import { pathToFileURL } from "url"; @@ -11,7 +10,6 @@ vi.mock("genlayer-js"); vi.mock("esbuild", () => ({ buildSync: vi.fn(), })); -vi.mock("../../src/lib/accounts/getPrivateKey"); describe("DeployAction", () => { let deployer: DeployAction; @@ -27,8 +25,8 @@ describe("DeployAction", () => { vi.clearAllMocks(); vi.mocked(createClient).mockReturnValue(mockClient as any); vi.mocked(createAccount).mockReturnValue({ privateKey: mockPrivateKey } as any); - vi.mocked(getPrivateKey).mockReturnValue(mockPrivateKey); deployer = new DeployAction(); + vi.spyOn(deployer as any, "getPrivateKey").mockResolvedValue(mockPrivateKey); vi.spyOn(deployer as any, "startSpinner").mockImplementation(() => {}); vi.spyOn(deployer as any, "succeedSpinner").mockImplementation(() => {}); @@ -286,7 +284,7 @@ describe("DeployAction", () => { await deployer["executeJsScript"](filePath); - expect(mockFn).toHaveBeenCalledWith(deployer["genlayerClient"]); + expect(mockFn).toHaveBeenCalledWith(mockClient); expect(deployer["succeedSpinner"]).toHaveBeenCalledWith(`Successfully executed: ${filePath}`); }); diff --git a/tests/libs/accounts/KeypairManager.test.ts b/tests/libs/accounts/KeypairManager.test.ts new file mode 100644 index 00000000..aa8e5597 --- /dev/null +++ b/tests/libs/accounts/KeypairManager.test.ts @@ -0,0 +1,110 @@ +import { describe, test, vi, beforeEach, afterEach, expect } from "vitest"; +import { writeFileSync, existsSync, readFileSync } from "fs"; +import { ethers } from "ethers"; +import { KeypairManager } from "../../../src/lib/accounts/KeypairManager"; + +vi.mock("fs"); +vi.mock("ethers"); + +describe("KeypairManager", () => { + let keypairManager: KeypairManager; + const mockPrivateKey = "0xMockedPrivateKey"; + const mockAddress = "0xMockedAddress"; + const mockKeypairPath = "/mocked/path/keypair.json"; + const mockKeypairData = { + address: mockAddress, + privateKey: mockPrivateKey, + }; + + beforeEach(() => { + vi.clearAllMocks(); + keypairManager = new KeypairManager(); + vi.spyOn(keypairManager as any, "getConfigByKey").mockReturnValue(mockKeypairPath); + vi.spyOn(keypairManager as any, "writeConfig").mockImplementation(() => {}); + vi.spyOn(keypairManager as any, "getFilePath").mockReturnValue(mockKeypairPath); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("getPrivateKey", () => { + test("should return private key when keypair file exists and contains private key", () => { + vi.mocked(existsSync).mockReturnValue(true); + vi.mocked(readFileSync).mockReturnValue(JSON.stringify(mockKeypairData)); + + const result = keypairManager.getPrivateKey(); + + expect(result).toBe(mockPrivateKey); + expect(existsSync).toHaveBeenCalledWith(mockKeypairPath); + expect(readFileSync).toHaveBeenCalledWith(mockKeypairPath, "utf-8"); + }); + + test("should return empty string when keypair file does not exist", () => { + vi.mocked(existsSync).mockReturnValue(false); + + const result = keypairManager.getPrivateKey(); + + expect(result).toBe(""); + expect(existsSync).toHaveBeenCalledWith(mockKeypairPath); + expect(readFileSync).not.toHaveBeenCalled(); + }); + + test("should return empty string when keypair file exists but has no private key", () => { + vi.mocked(existsSync).mockReturnValue(true); + vi.mocked(readFileSync).mockReturnValue(JSON.stringify({ address: mockAddress })); + + const result = keypairManager.getPrivateKey(); + + expect(result).toBe(""); + expect(existsSync).toHaveBeenCalledWith(mockKeypairPath); + expect(readFileSync).toHaveBeenCalledWith(mockKeypairPath, "utf-8"); + }); + }); + + describe("createKeypair", () => { + test("should create new keypair and save it to file", () => { + const mockWallet = { + address: mockAddress, + privateKey: mockPrivateKey, + }; + vi.mocked(ethers.Wallet.createRandom).mockReturnValue(mockWallet as any); + vi.mocked(existsSync).mockReturnValue(false); + + keypairManager.createKeypair(); + + expect(ethers.Wallet.createRandom).toHaveBeenCalled(); + expect(writeFileSync).toHaveBeenCalledWith( + mockKeypairPath, + JSON.stringify(mockKeypairData, null, 2) + ); + expect(keypairManager["writeConfig"]).toHaveBeenCalledWith("keyPairPath", mockKeypairPath); + }); + + test("should throw error when file exists and overwrite is false", () => { + vi.mocked(existsSync).mockReturnValue(true); + + expect(() => keypairManager.createKeypair()).toThrow( + `The file at ${mockKeypairPath} already exists. Use the '--overwrite' option to replace it.` + ); + }); + + test("should overwrite existing file when overwrite is true", () => { + const mockWallet = { + address: mockAddress, + privateKey: mockPrivateKey, + }; + vi.mocked(ethers.Wallet.createRandom).mockReturnValue(mockWallet as any); + vi.mocked(existsSync).mockReturnValue(true); + + keypairManager.createKeypair("./keypair.json", true); + + expect(ethers.Wallet.createRandom).toHaveBeenCalled(); + expect(writeFileSync).toHaveBeenCalledWith( + mockKeypairPath, + JSON.stringify(mockKeypairData, null, 2) + ); + expect(keypairManager["writeConfig"]).toHaveBeenCalledWith("keyPairPath", mockKeypairPath); + }); + }); +}); \ No newline at end of file diff --git a/tests/libs/baseAction.test.ts b/tests/libs/baseAction.test.ts index 118eb61d..be1aeafc 100644 --- a/tests/libs/baseAction.test.ts +++ b/tests/libs/baseAction.test.ts @@ -189,4 +189,44 @@ describe("BaseAction", () => { expect(result).toBe(expected); }); + const mockPrivateKey = "mocked_private_key"; + + beforeEach(() => { + baseAction["keypairManager"] = { + getPrivateKey: vi.fn(), + createKeypair: vi.fn(), + getKeypairPath: vi.fn(), + setKeypairPath: vi.fn(), + } as any; + + }); + + test("should return private key when it exists", async () => { + vi.mocked(baseAction["keypairManager"].getPrivateKey).mockReturnValue(mockPrivateKey); + + const result = await baseAction["getPrivateKey"](); + + expect(result).toBe(mockPrivateKey); + expect(baseAction["keypairManager"].createKeypair).not.toHaveBeenCalled(); + }); + + test("should create new keypair when private key doesn't exist and user confirms", async () => { + vi.mocked(baseAction["keypairManager"].getPrivateKey) + .mockReturnValueOnce(undefined) + .mockReturnValueOnce(mockPrivateKey); + vi.mocked(inquirer.prompt).mockResolvedValue({ confirmAction: true }); + await baseAction["getPrivateKey"](); + + expect(baseAction["keypairManager"].createKeypair).toHaveBeenCalled(); + }); + + test("should exit when private key doesn't exist and user declines", async () => { + vi.mocked(baseAction["keypairManager"].getPrivateKey).mockReturnValueOnce(undefined); + vi.mocked(inquirer.prompt).mockResolvedValue({ confirmAction: false }); + vi.spyOn(process, "exit").mockImplementation(() => { + throw new Error("process exited"); + }); + + await expect(baseAction["getPrivateKey"]()).rejects.toThrow("process exited"); + }); }); diff --git a/tests/libs/getPrivateKey.test.ts b/tests/libs/getPrivateKey.test.ts deleted file mode 100644 index 780ac798..00000000 --- a/tests/libs/getPrivateKey.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { describe, test, vi, beforeEach, afterEach, expect } from "vitest"; -import fs from "fs"; -import { getPrivateKey } from "../../src/lib/accounts/getPrivateKey"; -import { ConfigFileManager } from "../../src/lib/config/ConfigFileManager"; - -vi.mock("fs"); -vi.mock("../../src/lib/config/ConfigFileManager"); - -describe("getPrivateKey", () => { - new ConfigFileManager(); - - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - test("returns the private key if the file exists and is valid", () => { - const mockPath = "/mocked/path/keypair.json"; - const mockPrivateKey = "0xMockedPrivateKey"; - const mockKeypairData = { privateKey: mockPrivateKey }; - - vi.mocked(ConfigFileManager.prototype.getConfigByKey).mockReturnValue(mockPath); - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockKeypairData)); - - const privateKey = getPrivateKey(); - - expect(ConfigFileManager.prototype.getConfigByKey).toHaveBeenCalledWith("keyPairPath"); - expect(fs.existsSync).toHaveBeenCalledWith(mockPath); - expect(fs.readFileSync).toHaveBeenCalledWith(mockPath, "utf-8"); - expect(privateKey).toBe(mockPrivateKey); - }); - - test("exits if the keypair path is missing in the config", () => { - const consoleErrorSpy = vi.spyOn(console, "error"); - const processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => { - throw new Error("process.exit"); - }); - - vi.mocked(ConfigFileManager.prototype.getConfigByKey).mockReturnValue(null); - - expect(() => getPrivateKey()).toThrowError("process.exit"); - - expect(ConfigFileManager.prototype.getConfigByKey).toHaveBeenCalledWith("keyPairPath"); - expect(consoleErrorSpy).toHaveBeenCalledWith( - "Keypair file not found. Please generate or specify a valid keypair path." - ); - expect(processExitSpy).toHaveBeenCalledWith(1); - }); - - test("exits if the keypair file does not exist", () => { - const consoleErrorSpy = vi.spyOn(console, "error"); - const processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => { - throw new Error("process.exit"); - }); - - const mockPath = "/mocked/path/keypair.json"; - - vi.mocked(ConfigFileManager.prototype.getConfigByKey).mockReturnValue(mockPath); - vi.mocked(fs.existsSync).mockReturnValue(false); - - expect(() => getPrivateKey()).toThrowError("process.exit"); - - expect(ConfigFileManager.prototype.getConfigByKey).toHaveBeenCalledWith("keyPairPath"); - expect(fs.existsSync).toHaveBeenCalledWith(mockPath); - expect(consoleErrorSpy).toHaveBeenCalledWith( - "Keypair file not found. Please generate or specify a valid keypair path." - ); - expect(processExitSpy).toHaveBeenCalledWith(1); - }); - - test("exits if the private key is missing in the keypair file", () => { - const consoleErrorSpy = vi.spyOn(console, "error"); - const processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => { - throw new Error("process.exit"); - }); - - const mockPath = "/mocked/path/keypair.json"; - const mockKeypairData = { notPrivateKey: "SomeOtherData" }; // Invalid keypair data - - vi.mocked(ConfigFileManager.prototype.getConfigByKey).mockReturnValue(mockPath); - vi.mocked(fs.existsSync).mockReturnValue(true); - vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(mockKeypairData)); - - expect(() => getPrivateKey()).toThrowError("process.exit"); - - expect(ConfigFileManager.prototype.getConfigByKey).toHaveBeenCalledWith("keyPairPath"); - expect(fs.existsSync).toHaveBeenCalledWith(mockPath); - expect(fs.readFileSync).toHaveBeenCalledWith(mockPath, "utf-8"); - expect(consoleErrorSpy).toHaveBeenCalledWith("Invalid keypair file. Private key is missing."); - expect(processExitSpy).toHaveBeenCalledWith(1); - }); -}); From 6eec8770cd4255bd3172c2138aaca2315f04d809 Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Thu, 24 Apr 2025 09:54:44 -0300 Subject: [PATCH 3/3] feat: adding getclient to baseactions --- src/commands/contracts/call.ts | 14 -------------- src/commands/contracts/deploy.ts | 13 ------------- src/lib/actions/BaseAction.ts | 17 ++++++++++++++++- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/commands/contracts/call.ts b/src/commands/contracts/call.ts index 73111940..d0b095cb 100644 --- a/src/commands/contracts/call.ts +++ b/src/commands/contracts/call.ts @@ -1,4 +1,3 @@ -import { createClient, createAccount } from "genlayer-js"; import { simulator } from "genlayer-js/chains"; import type { GenLayerClient } from "genlayer-js/types"; import { BaseAction } from "../../lib/actions/BaseAction"; @@ -8,23 +7,10 @@ export interface CallOptions { } export class CallAction extends BaseAction{ - private _genlayerClient: GenLayerClient | null = null; - constructor() { super(); } - private async getClient(): Promise> { - if (!this._genlayerClient) { - this._genlayerClient = createClient({ - chain: simulator, - endpoint: process.env.VITE_JSON_RPC_SERVER_URL, - account: createAccount(await this.getPrivateKey() as any), - }); - } - return this._genlayerClient; - } - async call({ contractAddress, method, diff --git a/src/commands/contracts/deploy.ts b/src/commands/contracts/deploy.ts index 04b904c1..d2971955 100644 --- a/src/commands/contracts/deploy.ts +++ b/src/commands/contracts/deploy.ts @@ -1,6 +1,5 @@ import fs from "fs"; import path from "path"; -import { createClient, createAccount } from "genlayer-js"; import { simulator } from "genlayer-js/chains"; import type { GenLayerClient } from "genlayer-js/types"; import { BaseAction } from "../../lib/actions/BaseAction"; @@ -14,24 +13,12 @@ export interface DeployOptions { } export class DeployAction extends BaseAction { - private _genlayerClient: GenLayerClient | null = null; private readonly deployDir = path.resolve(process.cwd(), "deploy"); constructor() { super(); } - private async getClient(): Promise> { - if (!this._genlayerClient) { - this._genlayerClient = createClient({ - chain: simulator, - endpoint: process.env.VITE_JSON_RPC_SERVER_URL, - account: createAccount(await this.getPrivateKey() as any), - }); - } - return this._genlayerClient; - } - private readContractCode(contractPath: string): string { if (!fs.existsSync(contractPath)) { throw new Error(`Contract file not found: ${contractPath}`); diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts index ad1bbda6..2ce4a211 100644 --- a/src/lib/actions/BaseAction.ts +++ b/src/lib/actions/BaseAction.ts @@ -3,10 +3,14 @@ import ora, { Ora } from "ora"; import chalk from "chalk"; import inquirer from "inquirer"; import { KeypairManager } from "../accounts/KeypairManager"; +import { createClient, createAccount } from "genlayer-js"; +import { localnet } from "genlayer-js/chains"; +import type { GenLayerClient } from "genlayer-js/types"; export class BaseAction extends ConfigFileManager { - private spinner: Ora; protected keypairManager: KeypairManager; + private spinner: Ora; + private _genlayerClient: GenLayerClient | null = null; constructor() { super() @@ -14,6 +18,17 @@ export class BaseAction extends ConfigFileManager { this.keypairManager = new KeypairManager(); } + protected async getClient(): Promise> { + if (!this._genlayerClient) { + this._genlayerClient = createClient({ + chain: localnet, + endpoint: process.env.VITE_JSON_RPC_SERVER_URL, + account: createAccount(await this.getPrivateKey() as any), + }); + } + return this._genlayerClient; + } + protected async getPrivateKey() { const privateKey = this.keypairManager.getPrivateKey(); if (privateKey) {