From 589e8be6548cc830846023ea494614a1df781ead Mon Sep 17 00:00:00 2001 From: prxgr4mm3r Date: Fri, 17 Nov 2023 20:12:08 +0200 Subject: [PATCH 1/4] Add detection of build mode from metadata on deploy --- src/commands/contract/deploy.ts | 16 ++++++++++++++++ src/lib/contract.ts | 13 ++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/commands/contract/deploy.ts b/src/commands/contract/deploy.ts index 7b2e5482..22ca3d89 100644 --- a/src/commands/contract/deploy.ts +++ b/src/commands/contract/deploy.ts @@ -97,6 +97,22 @@ export class DeployContract extends SwankyCommand { return new ChainAccount(mnemonic); }, "Initialising")) as ChainAccount; + const buildMode = await contract.getBuildMode(); + if(buildMode !== 'Release') { + await inquirer.prompt([ + { + type: "confirm", + message: `You are deploying a contract in debug mode. Are you sure you want to continue?`, + name: "confirm", + }, + ]).then((answers) => { + if(!answers.confirm) { + this.log(`${chalk.redBright('✖')} Aborted deployment of ${chalk.yellowBright(args.contractName)}`); + process.exit(0); + } + }) + } + const { abi, wasm } = (await this.spinner.runCommand(async () => { const abi = await contract.getABI(); const wasm = await contract.getWasm(); diff --git a/src/lib/contract.ts b/src/lib/contract.ts index 482c0ae2..d65d4f42 100644 --- a/src/lib/contract.ts +++ b/src/lib/contract.ts @@ -3,6 +3,7 @@ import { ContractData, DeploymentData } from "../types/index.js"; import { pathExists, readJSON } from "fs-extra/esm"; import path from "node:path"; import { FileError } from "./errors.js"; +import fs from "fs"; export class Contract { static artifactTypes = [".json", ".contract"]; @@ -49,6 +50,16 @@ export class Contract { return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.json`)); } + async getBuildMode(): Promise { + const check = await this.artifactsExist(); + if (!check.result && check.missingTypes.includes(".contract")) { + throw new FileError( + `Cannot read .contract bundle, path not found: ${check.missingPaths.toString()}` + ); + } + const contractJson = JSON.parse(fs.readFileSync(path.resolve(this.artifactsPath, `${this.moduleName}.json`), 'utf8')); + return contractJson.source.build_info.build_mode; + } async getBundle() { const check = await this.artifactsExist(); if (!check.result && check.missingTypes.includes(".contract")) { @@ -56,7 +67,7 @@ export class Contract { `Cannot read .contract bundle, path not found: ${check.missingPaths.toString()}` ); } - return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.contract`)); + return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.contract`), 'utf8'); } async getWasm(): Promise { From a45ae84dc19e4a8ac1d9eec7f59cc21e89a9c44a Mon Sep 17 00:00:00 2001 From: prxgr4mm3r Date: Tue, 6 Feb 2024 00:12:20 +0200 Subject: [PATCH 2/4] fix: Add build mod enum and check verifiable build --- src/commands/contract/deploy.ts | 8 ++++---- src/lib/contract.ts | 6 +++--- src/types/index.ts | 6 ++++++ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/commands/contract/deploy.ts b/src/commands/contract/deploy.ts index 22ca3d89..6eaef42d 100644 --- a/src/commands/contract/deploy.ts +++ b/src/commands/contract/deploy.ts @@ -2,8 +2,8 @@ import { Args, Flags } from "@oclif/core"; import path from "node:path"; import { writeJSON } from "fs-extra/esm"; import { cryptoWaitReady } from "@polkadot/util-crypto/crypto"; -import { resolveNetworkUrl, ChainApi, ChainAccount, decrypt, AbiType } from "../../lib/index.js"; -import { AccountData, Encrypted } from "../../types/index.js"; +import { AbiType, ChainAccount, ChainApi, decrypt, resolveNetworkUrl } from "../../lib/index.js"; +import { AccountData, BuildMode, Encrypted } from "../../types/index.js"; import inquirer from "inquirer"; import chalk from "chalk"; import { Contract } from "../../lib/contract.js"; @@ -98,11 +98,11 @@ export class DeployContract extends SwankyCommand { }, "Initialising")) as ChainAccount; const buildMode = await contract.getBuildMode(); - if(buildMode !== 'Release') { + if(buildMode !== BuildMode.Verifiable) { await inquirer.prompt([ { type: "confirm", - message: `You are deploying a contract in debug mode. Are you sure you want to continue?`, + message: `You are deploying a not verified contract in ${buildMode === BuildMode.Release ? "release" : "debug"} mode. Are you sure you want to continue?`, name: "confirm", }, ]).then((answers) => { diff --git a/src/lib/contract.ts b/src/lib/contract.ts index d65d4f42..264ce382 100644 --- a/src/lib/contract.ts +++ b/src/lib/contract.ts @@ -1,5 +1,5 @@ import { AbiType, consts, printContractInfo } from "./index.js"; -import { ContractData, DeploymentData } from "../types/index.js"; +import { BuildMode, ContractData, DeploymentData } from "../types/index.js"; import { pathExists, readJSON } from "fs-extra/esm"; import path from "node:path"; import { FileError } from "./errors.js"; @@ -50,7 +50,7 @@ export class Contract { return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.json`)); } - async getBuildMode(): Promise { + async getBuildMode(): Promise { const check = await this.artifactsExist(); if (!check.result && check.missingTypes.includes(".contract")) { throw new FileError( @@ -58,7 +58,7 @@ export class Contract { ); } const contractJson = JSON.parse(fs.readFileSync(path.resolve(this.artifactsPath, `${this.moduleName}.json`), 'utf8')); - return contractJson.source.build_info.build_mode; + return contractJson.image && (contractJson.image as string).startsWith("paritytech/contracts-verifiable") ? BuildMode.Verifiable : contractJson.source.build_info.build_mode; } async getBundle() { const check = await this.artifactsExist(); diff --git a/src/types/index.ts b/src/types/index.ts index aa13a0df..1b3646a1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -49,5 +49,11 @@ export interface SwankyConfig { networks: Record } +export enum BuildMode { + Debug = "Debug", + Release = "Release", + Verifiable = "Verifiable", +} + export type SupportedPlatforms = "darwin" | "linux"; export type SupportedArch = "arm64" | "x64"; From f9515e4bfa2a3168bdfafeb80a95ee29c8b686fe Mon Sep 17 00:00:00 2001 From: Igor Papandinas Date: Tue, 6 Feb 2024 11:55:46 +0100 Subject: [PATCH 3/4] refactor: Improve readability for getBuildMode() --- src/lib/contract.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/contract.ts b/src/lib/contract.ts index 264ce382..a8972e78 100644 --- a/src/lib/contract.ts +++ b/src/lib/contract.ts @@ -54,12 +54,21 @@ export class Contract { const check = await this.artifactsExist(); if (!check.result && check.missingTypes.includes(".contract")) { throw new FileError( - `Cannot read .contract bundle, path not found: ${check.missingPaths.toString()}` + `Cannot read .contract bundle, path not found: ${check.missingPaths.join(', ')}` ); } - const contractJson = JSON.parse(fs.readFileSync(path.resolve(this.artifactsPath, `${this.moduleName}.json`), 'utf8')); - return contractJson.image && (contractJson.image as string).startsWith("paritytech/contracts-verifiable") ? BuildMode.Verifiable : contractJson.source.build_info.build_mode; + + const contractFilePath = path.resolve(this.artifactsPath, `${this.moduleName}.json`); + const contractFileContents = await fs.promises.readFile(contractFilePath, 'utf8'); + const contractJson = JSON.parse(contractFileContents); + + const isVerifiable = contractJson.image + && typeof contractJson.image === 'string' + && contractJson.image.startsWith("paritytech/contracts-verifiable"); + + return isVerifiable ? BuildMode.Verifiable : contractJson.source.build_info.build_mode; } + async getBundle() { const check = await this.artifactsExist(); if (!check.result && check.missingTypes.includes(".contract")) { From fdf90692963a34f06c585ed81d1032a7948d6bd0 Mon Sep 17 00:00:00 2001 From: Igor Papandinas Date: Mon, 26 Feb 2024 12:29:43 +0100 Subject: [PATCH 4/4] feat: buildMode added to contract build data --- src/commands/contract/compile.ts | 5 +++ src/commands/contract/deploy.ts | 45 +++++++++++++-------- src/lib/contract.ts | 69 ++++++++++++-------------------- src/types/index.ts | 1 + 4 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/commands/contract/compile.ts b/src/commands/contract/compile.ts index 56129c01..9d62c152 100644 --- a/src/commands/contract/compile.ts +++ b/src/commands/contract/compile.ts @@ -5,6 +5,7 @@ import { spawn } from "node:child_process"; import { pathExists } from "fs-extra/esm"; import { SwankyCommand } from "../../lib/swankyCommand.js"; import { ConfigError, InputError, ProcessError } from "../../lib/errors.js"; +import { BuildMode } from "../../index.js"; export class CompileContract extends SwankyCommand { static description = "Compile the smart contract(s) in your contracts directory"; @@ -63,6 +64,7 @@ export class CompileContract extends SwankyCommand { throw new InputError(`Contract folder not found at expected path`); } + let buildMode = BuildMode.Debug; const compilationResult = await spinner.runCommand( async () => { return new Promise((resolve, reject) => { @@ -73,9 +75,11 @@ export class CompileContract extends SwankyCommand { `contracts/${contractName}/Cargo.toml`, ]; if (flags.release && !flags.verifiable) { + buildMode = BuildMode.Release; compileArgs.push("--release"); } if (flags.verifiable) { + buildMode = BuildMode.Verifiable; const cargoContractVersion = extractCargoContractVersion(); if (cargoContractVersion === null) throw new InputError( @@ -135,6 +139,7 @@ export class CompileContract extends SwankyCommand { this.swankyConfig.contracts[contractName].build = { timestamp: Date.now(), artifactsPath, + buildMode, isVerified: false, }; diff --git a/src/commands/contract/deploy.ts b/src/commands/contract/deploy.ts index fbdfd670..ac3489b4 100644 --- a/src/commands/contract/deploy.ts +++ b/src/commands/contract/deploy.ts @@ -8,7 +8,7 @@ import inquirer from "inquirer"; import chalk from "chalk"; import { Contract } from "../../lib/contract.js"; import { SwankyCommand } from "../../lib/swankyCommand.js"; -import { ApiError, ConfigError, FileError } from "../../lib/errors.js"; +import { ApiError, ConfigError, FileError, ProcessError } from "../../lib/errors.js"; export class DeployContract extends SwankyCommand { static description = "Deploy contract to a running node"; @@ -70,6 +70,33 @@ export class DeployContract extends SwankyCommand { ); } + if (contract.buildMode === undefined) { + throw new ProcessError( + `Build mode is undefined for contract ${args.contractName}. Please ensure the contract is correctly compiled.` + ); + } else if (contract.buildMode !== BuildMode.Verifiable) { + await inquirer + .prompt([ + { + type: "confirm", + message: `You are deploying a not verified contract in ${ + contract.buildMode === BuildMode.Release ? "release" : "debug" + } mode. Are you sure you want to continue?`, + name: "confirm", + }, + ]) + .then((answers) => { + if (!answers.confirm) { + this.log( + `${chalk.redBright("✖")} Aborted deployment of ${chalk.yellowBright( + args.contractName + )}` + ); + process.exit(0); + } + }); + } + const accountData = this.findAccountByAlias(flags.account); const mnemonic = accountData.isDev ? (accountData.mnemonic as string) @@ -91,22 +118,6 @@ export class DeployContract extends SwankyCommand { return new ChainAccount(mnemonic); }, "Initialising")) as ChainAccount; - const buildMode = await contract.getBuildMode(); - if(buildMode !== BuildMode.Verifiable) { - await inquirer.prompt([ - { - type: "confirm", - message: `You are deploying a not verified contract in ${buildMode === BuildMode.Release ? "release" : "debug"} mode. Are you sure you want to continue?`, - name: "confirm", - }, - ]).then((answers) => { - if(!answers.confirm) { - this.log(`${chalk.redBright('✖')} Aborted deployment of ${chalk.yellowBright(args.contractName)}`); - process.exit(0); - } - }) - } - const { abi, wasm } = (await this.spinner.runCommand(async () => { const abi = await contract.getABI(); const wasm = await contract.getWasm(); diff --git a/src/lib/contract.ts b/src/lib/contract.ts index a8972e78..747bf48f 100644 --- a/src/lib/contract.ts +++ b/src/lib/contract.ts @@ -3,7 +3,6 @@ import { BuildMode, ContractData, DeploymentData } from "../types/index.js"; import { pathExists, readJSON } from "fs-extra/esm"; import path from "node:path"; import { FileError } from "./errors.js"; -import fs from "fs"; export class Contract { static artifactTypes = [".json", ".contract"]; @@ -12,77 +11,52 @@ export class Contract { deployments: DeploymentData[]; contractPath: string; artifactsPath: string; + buildMode?: BuildMode; + constructor(contractRecord: ContractData) { this.name = contractRecord.name; this.moduleName = contractRecord.moduleName; this.deployments = contractRecord.deployments; this.contractPath = path.resolve("contracts", contractRecord.name); this.artifactsPath = path.resolve(consts.ARTIFACTS_PATH, contractRecord.name); + this.buildMode = contractRecord.build?.buildMode; } async pathExists() { return pathExists(this.contractPath); } - async artifactsExist() { - const result: { result: boolean; missingPaths: string[]; missingTypes: string[] } = { - result: true, - missingPaths: [], - missingTypes: [], - }; + + async artifactsExist(): Promise<{ result: boolean; missingPaths: string[] }> { + const missingPaths: string[] = []; + let result = true; + for (const artifactType of Contract.artifactTypes) { const artifactPath = path.resolve(this.artifactsPath, `${this.moduleName}${artifactType}`); - if (!(await pathExists(artifactPath))) { - result.result = false; - result.missingPaths.push(artifactPath); - result.missingTypes.push(artifactType); + result = false; + missingPaths.push(artifactPath); } } - return result; - } - async getABI(): Promise { - const check = await this.artifactsExist(); - if (!check.result && check.missingTypes.includes(".json")) { - throw new FileError(`Cannot read ABI, path not found: ${check.missingPaths.toString()}`); - } - return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.json`)); + return { result, missingPaths }; } - async getBuildMode(): Promise { - const check = await this.artifactsExist(); - if (!check.result && check.missingTypes.includes(".contract")) { - throw new FileError( - `Cannot read .contract bundle, path not found: ${check.missingPaths.join(', ')}` - ); - } - - const contractFilePath = path.resolve(this.artifactsPath, `${this.moduleName}.json`); - const contractFileContents = await fs.promises.readFile(contractFilePath, 'utf8'); - const contractJson = JSON.parse(contractFileContents); - - const isVerifiable = contractJson.image - && typeof contractJson.image === 'string' - && contractJson.image.startsWith("paritytech/contracts-verifiable"); - - return isVerifiable ? BuildMode.Verifiable : contractJson.source.build_info.build_mode; + async getABI(): Promise { + const jsonArtifactPath = `${this.moduleName}.json`; + await this.ensureArtifactExists(jsonArtifactPath); + return readJSON(path.resolve(this.artifactsPath, jsonArtifactPath)); } async getBundle() { - const check = await this.artifactsExist(); - if (!check.result && check.missingTypes.includes(".contract")) { - throw new FileError( - `Cannot read .contract bundle, path not found: ${check.missingPaths.toString()}` - ); - } - return readJSON(path.resolve(this.artifactsPath, `${this.moduleName}.contract`), 'utf8'); + const contractArtifactPath = `${this.moduleName}.contract`; + await this.ensureArtifactExists(contractArtifactPath); + return readJSON(path.resolve(this.artifactsPath, contractArtifactPath), 'utf8'); } async getWasm(): Promise { const bundle = await this.getBundle(); if (bundle.source?.wasm) return bundle.source.wasm; - throw new FileError(`Cannot find wasm field in the .contract bundle!`); } @@ -90,4 +64,11 @@ export class Contract { const abi = await this.getABI(); printContractInfo(abi); } + + private async ensureArtifactExists(artifactFileName: string): Promise { + const artifactPath = path.resolve(this.artifactsPath, artifactFileName); + if (!(await pathExists(artifactPath))) { + throw new FileError(`Artifact file not found at path: ${artifactPath}`); + } + } } diff --git a/src/types/index.ts b/src/types/index.ts index fe63d3a5..1c7056f3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -30,6 +30,7 @@ export interface ContractData { export interface BuildData { timestamp: number; artifactsPath: string; + buildMode: BuildMode; isVerified: boolean; }